From 5b57d28ffb6e1ef86b50f7d05d977826eae89bfe Mon Sep 17 00:00:00 2001 From: Kiyoshi Aman Date: Fri, 1 Feb 2019 22:55:37 +0000 Subject: initial population --- LICENSE | 29 + README.md | 22 + bin/cat/cat.1 | 217 ++ bin/cat/cat.c | 321 +++ bin/chgrp/chgrp.1 | 164 ++ bin/chgrp/chown.8 | 181 ++ bin/chgrp/chown.c | 284 ++ bin/chmod/chmod.1 | 313 ++ bin/chmod/chmod.c | 239 ++ bin/chown/chgrp.1 | 169 ++ bin/chown/chown.8 | 184 ++ bin/chown/chown.c | 302 ++ bin/cp/cp.1 | 263 ++ bin/cp/cp.c | 548 ++++ bin/cp/extern.h | 61 + bin/cp/utils.c | 419 +++ bin/date/date.1 | 262 ++ bin/date/date.c | 364 +++ bin/date/extern.h | 39 + bin/date/netdate.c | 200 ++ bin/dd/args.c | 504 ++++ bin/dd/conv.c | 283 ++ bin/dd/conv_tab.c | 287 ++ bin/dd/dd.1 | 588 ++++ bin/dd/dd.c | 616 ++++ bin/dd/dd.h | 126 + bin/dd/dd_hostops.c | 53 + bin/dd/dd_rumpops.c | 52 + bin/dd/extern.h | 86 + bin/dd/misc.c | 342 +++ bin/dd/position.c | 185 ++ bin/df/df.1 | 206 ++ bin/df/df.c | 520 ++++ bin/echo/echo.1 | 71 + bin/echo/echo.c | 81 + bin/ed/POSIX | 103 + bin/ed/README | 23 + bin/ed/buf.c | 319 +++ bin/ed/cbc.c | 460 +++ bin/ed/ed.1 | 979 +++++++ bin/ed/ed.h | 265 ++ bin/ed/glbl.c | 213 ++ bin/ed/io.c | 358 +++ bin/ed/main.c | 1433 ++++++++++ bin/ed/re.c | 146 + bin/ed/sub.c | 262 ++ bin/ed/test/=.err | 1 + bin/ed/test/Makefile | 28 + bin/ed/test/README | 32 + bin/ed/test/TODO | 17 + bin/ed/test/a.d | 5 + bin/ed/test/a.r | 8 + bin/ed/test/a.t | 9 + bin/ed/test/a1.err | 3 + bin/ed/test/a2.err | 3 + bin/ed/test/addr.d | 9 + bin/ed/test/addr.r | 2 + bin/ed/test/addr.t | 5 + bin/ed/test/addr1.err | 1 + bin/ed/test/addr2.err | 1 + bin/ed/test/ascii.d | Bin 0 -> 256 bytes bin/ed/test/ascii.r | Bin 0 -> 256 bytes bin/ed/test/ascii.t | 0 bin/ed/test/bang1.d | 0 bin/ed/test/bang1.err | 1 + bin/ed/test/bang1.r | 1 + bin/ed/test/bang1.t | 5 + bin/ed/test/bang2.err | 1 + bin/ed/test/c.d | 5 + bin/ed/test/c.r | 4 + bin/ed/test/c.t | 12 + bin/ed/test/c1.err | 3 + bin/ed/test/c2.err | 3 + bin/ed/test/ckscripts.sh | 37 + bin/ed/test/d.d | 5 + bin/ed/test/d.err | 1 + bin/ed/test/d.r | 1 + bin/ed/test/d.t | 3 + bin/ed/test/e1.d | 1 + bin/ed/test/e1.err | 1 + bin/ed/test/e1.r | 1 + bin/ed/test/e1.t | 1 + bin/ed/test/e2.d | 1 + bin/ed/test/e2.err | 1 + bin/ed/test/e2.r | 1 + bin/ed/test/e2.t | 1 + bin/ed/test/e3.d | 1 + bin/ed/test/e3.err | 1 + bin/ed/test/e3.r | 1 + bin/ed/test/e3.t | 1 + bin/ed/test/e4.d | 1 + bin/ed/test/e4.r | 1 + bin/ed/test/e4.t | 1 + bin/ed/test/f1.err | 1 + bin/ed/test/f2.err | 1 + bin/ed/test/g1.d | 5 + bin/ed/test/g1.err | 1 + bin/ed/test/g1.r | 15 + bin/ed/test/g1.t | 6 + bin/ed/test/g2.d | 5 + bin/ed/test/g2.err | 1 + bin/ed/test/g2.r | 1 + bin/ed/test/g2.t | 2 + bin/ed/test/g3.d | 5 + bin/ed/test/g3.err | 1 + bin/ed/test/g3.r | 5 + bin/ed/test/g3.t | 4 + bin/ed/test/g4.d | 5 + bin/ed/test/g4.r | 7 + bin/ed/test/g4.t | 13 + bin/ed/test/g5.d | 3 + bin/ed/test/g5.r | 9 + bin/ed/test/g5.t | 2 + bin/ed/test/h.err | 1 + bin/ed/test/i.d | 5 + bin/ed/test/i.r | 8 + bin/ed/test/i.t | 9 + bin/ed/test/i1.err | 3 + bin/ed/test/i2.err | 3 + bin/ed/test/i3.err | 3 + bin/ed/test/j.d | 5 + bin/ed/test/j.r | 4 + bin/ed/test/j.t | 2 + bin/ed/test/k.d | 5 + bin/ed/test/k.r | 5 + bin/ed/test/k.t | 10 + bin/ed/test/k1.err | 1 + bin/ed/test/k2.err | 1 + bin/ed/test/k3.err | 1 + bin/ed/test/k4.err | 6 + bin/ed/test/l.d | 0 bin/ed/test/l.r | 0 bin/ed/test/l.t | 0 bin/ed/test/m.d | 5 + bin/ed/test/m.err | 4 + bin/ed/test/m.r | 5 + bin/ed/test/m.t | 7 + bin/ed/test/mkscripts.sh | 75 + bin/ed/test/n.d | 0 bin/ed/test/n.r | 0 bin/ed/test/n.t | 0 bin/ed/test/nl.err | 1 + bin/ed/test/nl1.d | 5 + bin/ed/test/nl1.r | 8 + bin/ed/test/nl1.t | 8 + bin/ed/test/nl2.d | 5 + bin/ed/test/nl2.r | 6 + bin/ed/test/nl2.t | 4 + bin/ed/test/p.d | 0 bin/ed/test/p.r | 0 bin/ed/test/p.t | 0 bin/ed/test/q.d | 0 bin/ed/test/q.r | 0 bin/ed/test/q.t | 5 + bin/ed/test/q1.err | 1 + bin/ed/test/r1.d | 5 + bin/ed/test/r1.err | 1 + bin/ed/test/r1.r | 7 + bin/ed/test/r1.t | 3 + bin/ed/test/r2.d | 5 + bin/ed/test/r2.err | 1 + bin/ed/test/r2.r | 10 + bin/ed/test/r2.t | 1 + bin/ed/test/r3.d | 1 + bin/ed/test/r3.r | 2 + bin/ed/test/r3.t | 1 + bin/ed/test/s1.d | 5 + bin/ed/test/s1.err | 1 + bin/ed/test/s1.r | 5 + bin/ed/test/s1.t | 6 + bin/ed/test/s10.err | 4 + bin/ed/test/s2.d | 5 + bin/ed/test/s2.err | 4 + bin/ed/test/s2.r | 5 + bin/ed/test/s2.t | 4 + bin/ed/test/s3.d | 0 bin/ed/test/s3.err | 1 + bin/ed/test/s3.r | 1 + bin/ed/test/s3.t | 6 + bin/ed/test/s4.err | 1 + bin/ed/test/s5.err | 1 + bin/ed/test/s6.err | 1 + bin/ed/test/s7.err | 5 + bin/ed/test/s8.err | 4 + bin/ed/test/s9.err | 4 + bin/ed/test/t.d | 5 + bin/ed/test/t.r | 16 + bin/ed/test/t1.d | 5 + bin/ed/test/t1.err | 1 + bin/ed/test/t1.r | 16 + bin/ed/test/t1.t | 3 + bin/ed/test/t2.d | 5 + bin/ed/test/t2.err | 1 + bin/ed/test/t2.r | 6 + bin/ed/test/t2.t | 1 + bin/ed/test/u.d | 5 + bin/ed/test/u.err | 1 + bin/ed/test/u.r | 9 + bin/ed/test/u.t | 31 + bin/ed/test/v.d | 5 + bin/ed/test/v.r | 11 + bin/ed/test/v.t | 6 + bin/ed/test/w.d | 5 + bin/ed/test/w.r | 10 + bin/ed/test/w.t | 2 + bin/ed/test/w1.err | 1 + bin/ed/test/w2.err | 1 + bin/ed/test/w3.err | 1 + bin/ed/test/x.err | 1 + bin/ed/test/z.err | 2 + bin/ed/undo.c | 150 + bin/expr/expr.1 | 280 ++ bin/expr/expr.y | 467 +++ bin/kill/kill.1 | 156 + bin/kill/kill.c | 320 +++ bin/ln/ln.1 | 320 +++ bin/ln/ln.c | 365 +++ bin/ls/cmp.c | 199 ++ bin/ls/extern.h | 53 + bin/ls/ls.1 | 516 ++++ bin/ls/ls.c | 715 +++++ bin/ls/ls.h | 81 + bin/ls/main.c | 50 + bin/ls/print.c | 497 ++++ bin/ls/util.c | 168 ++ bin/mkdir/mkdir.1 | 97 + bin/mkdir/mkdir.c | 223 ++ bin/mv/mv.1 | 149 + bin/mv/mv.c | 427 +++ bin/mv/pathnames.h | 40 + bin/pax/ar_io.c | 1725 +++++++++++ bin/pax/ar_subs.c | 1449 ++++++++++ bin/pax/buf_subs.c | 1022 +++++++ bin/pax/cpio.1 | 307 ++ bin/pax/cpio.c | 1134 ++++++++ bin/pax/cpio.h | 149 + bin/pax/dumptar.c | 131 + bin/pax/extern.h | 326 +++ bin/pax/file_subs.c | 1156 ++++++++ bin/pax/ftree.c | 741 +++++ bin/pax/ftree.h | 48 + bin/pax/gen_subs.c | 437 +++ bin/pax/getoldopt.c | 92 + bin/pax/options.c | 2229 +++++++++++++++ bin/pax/options.h | 116 + bin/pax/pat_rep.c | 1139 ++++++++ bin/pax/pat_rep.h | 51 + bin/pax/pax.1 | 1304 +++++++++ bin/pax/pax.c | 492 ++++ bin/pax/pax.h | 283 ++ bin/pax/sel_subs.c | 617 ++++ bin/pax/sel_subs.h | 69 + bin/pax/tables.c | 1379 +++++++++ bin/pax/tables.h | 176 ++ bin/pax/tar.1 | 372 +++ bin/pax/tar.c | 1430 ++++++++++ bin/pax/tar.h | 154 + bin/pax/tty_subs.c | 200 ++ bin/ps/extern.h | 99 + bin/ps/fmt.c | 60 + bin/ps/keyword.c | 415 +++ bin/ps/nlist.c | 234 ++ bin/ps/print.c | 1413 ++++++++++ bin/ps/ps.1 | 706 +++++ bin/ps/ps.c | 947 +++++++ bin/ps/ps.h | 104 + bin/pwd/pwd.1 | 110 + bin/pwd/pwd.c | 143 + bin/rm/rm.1 | 215 ++ bin/rm/rm.c | 611 ++++ bin/rmdir/rmdir.1 | 95 + bin/rmdir/rmdir.c | 125 + bin/sh/TOUR | 357 +++ bin/sh/USD.doc/Makefile | 12 + bin/sh/USD.doc/Rv7man | 405 +++ bin/sh/USD.doc/referargs | 8 + bin/sh/USD.doc/t.mac | 69 + bin/sh/USD.doc/t1 | 553 ++++ bin/sh/USD.doc/t2 | 971 +++++++ bin/sh/USD.doc/t3 | 976 +++++++ bin/sh/USD.doc/t4 | 180 ++ bin/sh/alias.c | 314 +++ bin/sh/alias.h | 48 + bin/sh/arith_token.c | 262 ++ bin/sh/arith_tokens.h | 120 + bin/sh/arithmetic.c | 502 ++++ bin/sh/arithmetic.h | 42 + bin/sh/bltin/bltin.h | 105 + bin/sh/bltin/echo.1 | 109 + bin/sh/bltin/echo.c | 122 + bin/sh/builtins.def | 98 + bin/sh/cd.c | 463 +++ bin/sh/cd.h | 34 + bin/sh/error.c | 378 +++ bin/sh/error.h | 120 + bin/sh/eval.c | 1680 +++++++++++ bin/sh/eval.h | 87 + bin/sh/exec.c | 1183 ++++++++ bin/sh/exec.h | 78 + bin/sh/expand.c | 2125 ++++++++++++++ bin/sh/expand.h | 71 + bin/sh/funcs/cmv | 43 + bin/sh/funcs/dirs | 67 + bin/sh/funcs/kill | 43 + bin/sh/funcs/login | 32 + bin/sh/funcs/newgrp | 31 + bin/sh/funcs/popd | 67 + bin/sh/funcs/pushd | 67 + bin/sh/funcs/suspend | 35 + bin/sh/histedit.c | 576 ++++ bin/sh/init.h | 39 + bin/sh/input.c | 695 +++++ bin/sh/input.h | 69 + bin/sh/jobs.c | 1812 ++++++++++++ bin/sh/jobs.h | 105 + bin/sh/machdep.h | 47 + bin/sh/mail.c | 144 + bin/sh/mail.h | 37 + bin/sh/main.c | 393 +++ bin/sh/main.h | 42 + bin/sh/memalloc.c | 334 +++ bin/sh/memalloc.h | 79 + bin/sh/miscbltin.c | 458 +++ bin/sh/miscbltin.h | 31 + bin/sh/mkbuiltins | 136 + bin/sh/mkinit.sh | 224 ++ bin/sh/mknodenames.sh | 69 + bin/sh/mknodes.sh | 242 ++ bin/sh/mkoptions.sh | 198 ++ bin/sh/mktokens | 99 + bin/sh/myhistedit.h | 48 + bin/sh/mystring.c | 140 + bin/sh/mystring.h | 45 + bin/sh/nodes.c.pat | 210 ++ bin/sh/nodetypes | 149 + bin/sh/option.list | 79 + bin/sh/options.c | 631 +++++ bin/sh/options.h | 72 + bin/sh/output.c | 755 +++++ bin/sh/output.h | 109 + bin/sh/parser.c | 2756 ++++++++++++++++++ bin/sh/parser.h | 169 ++ bin/sh/redir.c | 982 +++++++ bin/sh/redir.h | 55 + bin/sh/sh.1 | 4549 ++++++++++++++++++++++++++++++ bin/sh/shell.h | 224 ++ bin/sh/show.c | 1175 ++++++++ bin/sh/show.h | 46 + bin/sh/syntax.c | 111 + bin/sh/syntax.h | 98 + bin/sh/trap.c | 837 ++++++ bin/sh/trap.h | 52 + bin/sh/var.c | 1587 +++++++++++ bin/sh/var.h | 151 + bin/sh/version.h | 36 + bin/sleep/sleep.1 | 177 ++ bin/sleep/sleep.c | 229 ++ bin/stty/cchar.c | 148 + bin/stty/extern.h | 51 + bin/stty/gfmt.c | 132 + bin/stty/key.c | 332 +++ bin/stty/modes.c | 224 ++ bin/stty/print.c | 257 ++ bin/stty/stty.1 | 635 +++++ bin/stty/stty.c | 168 ++ bin/stty/stty.h | 62 + bin/test/TEST.csh | 138 + bin/test/test.1 | 383 +++ bin/test/test.c | 904 ++++++ usr.bin/asa/asa.1 | 85 + usr.bin/asa/asa.c | 116 + usr.bin/at/at.1 | 339 +++ usr.bin/at/at.c | 758 +++++ usr.bin/at/at.h | 43 + usr.bin/at/panic.c | 110 + usr.bin/at/panic.h | 37 + usr.bin/at/parsetime.c | 652 +++++ usr.bin/at/parsetime.h | 35 + usr.bin/at/pathnames.h | 51 + usr.bin/at/perm.c | 118 + usr.bin/at/perm.h | 33 + usr.bin/at/privs.c | 104 + usr.bin/at/privs.h | 76 + usr.bin/at/stime.c | 116 + usr.bin/at/stime.h | 53 + usr.bin/basename/basename.1 | 92 + usr.bin/basename/basename.c | 103 + usr.bin/c99/c99.1 | 81 + usr.bin/c99/c99.sh | 2 + usr.bin/cal/README | 42 + usr.bin/cal/cal.1 | 164 ++ usr.bin/cal/cal.c | 924 ++++++ usr.bin/cksum/cksum.1 | 341 +++ usr.bin/cksum/cksum.c | 551 ++++ usr.bin/cksum/crc.c | 162 ++ usr.bin/cksum/crc_extern.h | 38 + usr.bin/cksum/extern.h | 88 + usr.bin/cksum/md2.c | 21 + usr.bin/cksum/md4.c | 21 + usr.bin/cksum/md5.c | 153 + usr.bin/cksum/print.c | 76 + usr.bin/cksum/rmd160.c | 25 + usr.bin/cksum/sha1.c | 21 + usr.bin/cksum/sha256.c | 25 + usr.bin/cksum/sha384.c | 25 + usr.bin/cksum/sha512.c | 25 + usr.bin/cksum/sum1.c | 76 + usr.bin/cksum/sum2.c | 80 + usr.bin/cmp/cmp.1 | 122 + usr.bin/cmp/cmp.c | 168 ++ usr.bin/cmp/extern.h | 44 + usr.bin/cmp/misc.c | 84 + usr.bin/cmp/regular.c | 118 + usr.bin/cmp/special.c | 111 + usr.bin/comm/comm.1 | 102 + usr.bin/comm/comm.c | 213 ++ usr.bin/compress/compress.1 | 171 ++ usr.bin/compress/compress.c | 459 +++ usr.bin/compress/doc/NOTES | 139 + usr.bin/compress/doc/README | 283 ++ usr.bin/compress/doc/revision.log | 116 + usr.bin/compress/zopen.3 | 138 + usr.bin/compress/zopen.c | 699 +++++ usr.bin/csplit/csplit.1 | 163 ++ usr.bin/csplit/csplit.c | 479 ++++ usr.bin/ctags/C.c | 573 ++++ usr.bin/ctags/ctags.1 | 225 ++ usr.bin/ctags/ctags.c | 275 ++ usr.bin/ctags/ctags.h | 92 + usr.bin/ctags/fortran.c | 174 ++ usr.bin/ctags/lisp.c | 112 + usr.bin/ctags/print.c | 121 + usr.bin/ctags/test/ctags.test | 69 + usr.bin/ctags/tree.c | 145 + usr.bin/ctags/yacc.c | 160 ++ usr.bin/cut/cut.1 | 126 + usr.bin/cut/cut.c | 306 ++ usr.bin/cut/x_cut.c | 95 + usr.bin/dirname/dirname.c | 86 + usr.bin/du/du.1 | 168 ++ usr.bin/du/du.c | 362 +++ usr.bin/env/env.1 | 122 + usr.bin/env/env.c | 99 + usr.bin/expand/expand.1 | 87 + usr.bin/expand/expand.c | 182 ++ usr.bin/false/false.1 | 56 + usr.bin/false/false.sh | 2 + usr.bin/find/extern.h | 102 + usr.bin/find/find.1 | 974 +++++++ usr.bin/find/find.c | 306 ++ usr.bin/find/find.h | 138 + usr.bin/find/function.c | 2025 +++++++++++++ usr.bin/find/ls.c | 124 + usr.bin/find/main.c | 164 ++ usr.bin/find/misc.c | 151 + usr.bin/find/operator.c | 272 ++ usr.bin/find/option.c | 194 ++ usr.bin/fold/fold.1 | 93 + usr.bin/fold/fold.c | 248 ++ usr.bin/gencat/gencat.1 | 233 ++ usr.bin/gencat/gencat.c | 879 ++++++ usr.bin/getconf/getconf.1 | 94 + usr.bin/getconf/getconf.c | 342 +++ usr.bin/grep/fastgrep.c | 336 +++ usr.bin/grep/file.c | 276 ++ usr.bin/grep/grep.1 | 486 ++++ usr.bin/grep/grep.c | 722 +++++ usr.bin/grep/grep.h | 162 ++ usr.bin/grep/nls/C.msg | 13 + usr.bin/grep/nls/es_ES.ISO8859-1.msg | 14 + usr.bin/grep/nls/gl_ES.ISO8859-1.msg | 14 + usr.bin/grep/nls/hu_HU.ISO8859-2.msg | 14 + usr.bin/grep/nls/ja_JP.SJIS.msg | 14 + usr.bin/grep/nls/ja_JP.UTF-8.msg | 14 + usr.bin/grep/nls/ja_JP.eucJP.msg | 14 + usr.bin/grep/nls/pt_BR.ISO8859-1.msg | 14 + usr.bin/grep/nls/ru_RU.KOI8-R.msg | 14 + usr.bin/grep/nls/uk_UA.UTF-8.msg | 13 + usr.bin/grep/nls/zh_CN.UTF-8.msg | 14 + usr.bin/grep/queue.c | 116 + usr.bin/grep/util.c | 500 ++++ usr.bin/head/head.1 | 95 + usr.bin/head/head.c | 204 ++ usr.bin/iconv/iconv.1 | 122 + usr.bin/iconv/iconv.c | 236 ++ usr.bin/id/groups.1 | 59 + usr.bin/id/id.1 | 136 + usr.bin/id/id.c | 370 +++ usr.bin/id/whoami.1 | 58 + usr.bin/ipcrm/ipcrm.1 | 97 + usr.bin/ipcrm/ipcrm.c | 313 ++ usr.bin/ipcs/ipcs.1 | 141 + usr.bin/ipcs/ipcs.c | 678 +++++ usr.bin/join/join.1 | 206 ++ usr.bin/join/join.c | 637 +++++ usr.bin/ldd/Makefile.common | 16 + usr.bin/ldd/Makefile.elf | 10 + usr.bin/ldd/build/Makefile | 42 + usr.bin/ldd/dummy.c | 3 + usr.bin/ldd/elf32/Makefile | 33 + usr.bin/ldd/elf32_compat/Makefile | 26 + usr.bin/ldd/elf64/Makefile | 51 + usr.bin/ldd/ldd.1 | 126 + usr.bin/ldd/ldd.c | 237 ++ usr.bin/ldd/ldd.h | 47 + usr.bin/ldd/ldd_elfxx.c | 269 ++ usr.bin/locale/locale.1 | 113 + usr.bin/locale/locale.c | 707 +++++ usr.bin/logger/logger.1 | 117 + usr.bin/logger/logger.c | 203 ++ usr.bin/logname/logname.1 | 72 + usr.bin/logname/logname.c | 85 + usr.bin/m4/NOTES | 64 + usr.bin/m4/PSD.doc/Makefile | 10 + usr.bin/m4/TEST/ack.m4 | 41 + usr.bin/m4/TEST/hanoi.m4 | 46 + usr.bin/m4/TEST/hash.m4 | 56 + usr.bin/m4/TEST/math.m4 | 182 ++ usr.bin/m4/TEST/sqroot.m4 | 46 + usr.bin/m4/TEST/string.m4 | 46 + usr.bin/m4/TEST/test.m4 | 244 ++ usr.bin/m4/eval.c | 1053 +++++++ usr.bin/m4/expr.c | 50 + usr.bin/m4/extern.h | 190 ++ usr.bin/m4/gnum4.c | 817 ++++++ usr.bin/m4/lib/ohash.h | 73 + usr.bin/m4/lib/ohash_create_entry.c | 38 + usr.bin/m4/lib/ohash_delete.c | 31 + usr.bin/m4/lib/ohash_do.c | 111 + usr.bin/m4/lib/ohash_entries.c | 26 + usr.bin/m4/lib/ohash_enum.c | 36 + usr.bin/m4/lib/ohash_init.3 | 229 ++ usr.bin/m4/lib/ohash_init.c | 41 + usr.bin/m4/lib/ohash_int.h | 23 + usr.bin/m4/lib/ohash_interval.3 | 90 + usr.bin/m4/lib/ohash_interval.c | 36 + usr.bin/m4/lib/ohash_lookup_interval.c | 68 + usr.bin/m4/lib/ohash_lookup_memory.c | 64 + usr.bin/m4/lib/ohash_qlookup.c | 27 + usr.bin/m4/lib/ohash_qlookupi.c | 29 + usr.bin/m4/lib/strtonum.c | 73 + usr.bin/m4/look.c | 315 +++ usr.bin/m4/m4.1 | 522 ++++ usr.bin/m4/main.c | 811 ++++++ usr.bin/m4/mdef.h | 237 ++ usr.bin/m4/misc.c | 414 +++ usr.bin/m4/parser.y | 86 + usr.bin/m4/pathnames.h | 40 + usr.bin/m4/stdd.h | 54 + usr.bin/m4/tokenizer.l | 108 + usr.bin/m4/trace.c | 203 ++ usr.bin/make/Makefile.boot | 45 + usr.bin/make/PSD.doc/Makefile | 10 + usr.bin/make/PSD.doc/tutorial.ms | 3794 +++++++++++++++++++++++++ usr.bin/make/arch.c | 1369 +++++++++ usr.bin/make/buf.c | 291 ++ usr.bin/make/buf.h | 119 + usr.bin/make/compat.c | 778 +++++ usr.bin/make/cond.c | 1436 ++++++++++ usr.bin/make/config.h | 160 ++ usr.bin/make/dir.c | 1849 ++++++++++++ usr.bin/make/dir.h | 108 + usr.bin/make/for.c | 496 ++++ usr.bin/make/hash.c | 466 +++ usr.bin/make/hash.h | 149 + usr.bin/make/job.c | 3070 ++++++++++++++++++++ usr.bin/make/job.h | 274 ++ usr.bin/make/lst.h | 189 ++ usr.bin/make/lst.lib/Makefile | 10 + usr.bin/make/lst.lib/lstAppend.c | 122 + usr.bin/make/lst.lib/lstAtEnd.c | 79 + usr.bin/make/lst.lib/lstAtFront.c | 76 + usr.bin/make/lst.lib/lstClose.c | 86 + usr.bin/make/lst.lib/lstConcat.c | 185 ++ usr.bin/make/lst.lib/lstDatum.c | 77 + usr.bin/make/lst.lib/lstDeQueue.c | 87 + usr.bin/make/lst.lib/lstDestroy.c | 101 + usr.bin/make/lst.lib/lstDupl.c | 107 + usr.bin/make/lst.lib/lstEnQueue.c | 78 + usr.bin/make/lst.lib/lstFind.c | 74 + usr.bin/make/lst.lib/lstFindFrom.c | 90 + usr.bin/make/lst.lib/lstFirst.c | 77 + usr.bin/make/lst.lib/lstForEach.c | 76 + usr.bin/make/lst.lib/lstForEachFrom.c | 125 + usr.bin/make/lst.lib/lstInit.c | 85 + usr.bin/make/lst.lib/lstInsert.c | 122 + usr.bin/make/lst.lib/lstInt.h | 105 + usr.bin/make/lst.lib/lstIsAtEnd.c | 87 + usr.bin/make/lst.lib/lstIsEmpty.c | 75 + usr.bin/make/lst.lib/lstLast.c | 77 + usr.bin/make/lst.lib/lstMember.c | 77 + usr.bin/make/lst.lib/lstNext.c | 120 + usr.bin/make/lst.lib/lstOpen.c | 87 + usr.bin/make/lst.lib/lstPrev.c | 79 + usr.bin/make/lst.lib/lstRemove.c | 136 + usr.bin/make/lst.lib/lstReplace.c | 78 + usr.bin/make/lst.lib/lstSucc.c | 79 + usr.bin/make/main.c | 2189 ++++++++++++++ usr.bin/make/make.1 | 2413 ++++++++++++++++ usr.bin/make/make.c | 1555 ++++++++++ usr.bin/make/make.h | 535 ++++ usr.bin/make/make_malloc.c | 119 + usr.bin/make/make_malloc.h | 41 + usr.bin/make/meta.c | 1641 +++++++++++ usr.bin/make/meta.h | 56 + usr.bin/make/metachar.c | 88 + usr.bin/make/metachar.h | 61 + usr.bin/make/nonints.h | 198 ++ usr.bin/make/parse.c | 3357 ++++++++++++++++++++++ usr.bin/make/pathnames.h | 53 + usr.bin/make/sprite.h | 116 + usr.bin/make/str.c | 526 ++++ usr.bin/make/strlist.c | 93 + usr.bin/make/strlist.h | 62 + usr.bin/make/suff.c | 2676 ++++++++++++++++++ usr.bin/make/targ.c | 846 ++++++ usr.bin/make/trace.c | 116 + usr.bin/make/trace.h | 49 + usr.bin/make/unit-tests/Makefile | 139 + usr.bin/make/unit-tests/comment.exp | 5 + usr.bin/make/unit-tests/comment.mk | 31 + usr.bin/make/unit-tests/cond1.exp | 23 + usr.bin/make/unit-tests/cond1.mk | 109 + usr.bin/make/unit-tests/cond2.exp | 7 + usr.bin/make/unit-tests/cond2.mk | 29 + usr.bin/make/unit-tests/doterror.exp | 9 + usr.bin/make/unit-tests/doterror.mk | 20 + usr.bin/make/unit-tests/dotwait.exp | 30 + usr.bin/make/unit-tests/dotwait.mk | 61 + usr.bin/make/unit-tests/error.exp | 4 + usr.bin/make/unit-tests/error.mk | 10 + usr.bin/make/unit-tests/escape.exp | 104 + usr.bin/make/unit-tests/escape.mk | 246 ++ usr.bin/make/unit-tests/export-all.exp | 12 + usr.bin/make/unit-tests/export-all.mk | 23 + usr.bin/make/unit-tests/export-env.exp | 11 + usr.bin/make/unit-tests/export-env.mk | 27 + usr.bin/make/unit-tests/export.exp | 6 + usr.bin/make/unit-tests/export.mk | 22 + usr.bin/make/unit-tests/forloop.exp | 19 + usr.bin/make/unit-tests/forloop.mk | 45 + usr.bin/make/unit-tests/forsubst.exp | 2 + usr.bin/make/unit-tests/forsubst.mk | 10 + usr.bin/make/unit-tests/hash.exp | 9 + usr.bin/make/unit-tests/hash.mk | 18 + usr.bin/make/unit-tests/impsrc.exp | 13 + usr.bin/make/unit-tests/impsrc.mk | 43 + usr.bin/make/unit-tests/misc.exp | 1 + usr.bin/make/unit-tests/misc.mk | 16 + usr.bin/make/unit-tests/moderrs.exp | 16 + usr.bin/make/unit-tests/moderrs.mk | 31 + usr.bin/make/unit-tests/modmatch.exp | 20 + usr.bin/make/unit-tests/modmatch.mk | 34 + usr.bin/make/unit-tests/modmisc.exp | 10 + usr.bin/make/unit-tests/modmisc.mk | 38 + usr.bin/make/unit-tests/modorder.exp | 11 + usr.bin/make/unit-tests/modorder.mk | 22 + usr.bin/make/unit-tests/modts.exp | 39 + usr.bin/make/unit-tests/modts.mk | 44 + usr.bin/make/unit-tests/modword.exp | 122 + usr.bin/make/unit-tests/modword.mk | 151 + usr.bin/make/unit-tests/order.exp | 4 + usr.bin/make/unit-tests/order.mk | 20 + usr.bin/make/unit-tests/phony-end.exp | 6 + usr.bin/make/unit-tests/phony-end.mk | 9 + usr.bin/make/unit-tests/posix.exp | 23 + usr.bin/make/unit-tests/posix.mk | 24 + usr.bin/make/unit-tests/posix1.exp | 186 ++ usr.bin/make/unit-tests/posix1.mk | 184 ++ usr.bin/make/unit-tests/qequals.exp | 2 + usr.bin/make/unit-tests/qequals.mk | 8 + usr.bin/make/unit-tests/suffixes.exp | 35 + usr.bin/make/unit-tests/suffixes.mk | 89 + usr.bin/make/unit-tests/sunshcmd.exp | 4 + usr.bin/make/unit-tests/sunshcmd.mk | 10 + usr.bin/make/unit-tests/sysv.exp | 7 + usr.bin/make/unit-tests/sysv.mk | 26 + usr.bin/make/unit-tests/ternary.exp | 10 + usr.bin/make/unit-tests/ternary.mk | 8 + usr.bin/make/unit-tests/unexport-env.exp | 2 + usr.bin/make/unit-tests/unexport-env.mk | 14 + usr.bin/make/unit-tests/unexport.exp | 4 + usr.bin/make/unit-tests/unexport.mk | 8 + usr.bin/make/unit-tests/varcmd.exp | 11 + usr.bin/make/unit-tests/varcmd.mk | 60 + usr.bin/make/unit-tests/varmisc.exp | 25 + usr.bin/make/unit-tests/varmisc.mk | 62 + usr.bin/make/unit-tests/varquote.exp | 3 + usr.bin/make/unit-tests/varquote.mk | 14 + usr.bin/make/unit-tests/varshell.exp | 12 + usr.bin/make/unit-tests/varshell.mk | 18 + usr.bin/make/util.c | 494 ++++ usr.bin/make/var.c | 4355 ++++++++++++++++++++++++++++ usr.bin/man/man.1 | 269 ++ usr.bin/man/man.c | 1077 +++++++ usr.bin/man/man.conf.5 | 286 ++ usr.bin/man/manconf.c | 272 ++ usr.bin/man/manconf.h | 60 + usr.bin/man/pathnames.h | 39 + usr.bin/mesg/mesg.1 | 89 + usr.bin/mesg/mesg.c | 108 + usr.bin/mkfifo/mkfifo.1 | 86 + usr.bin/mkfifo/mkfifo.c | 107 + usr.bin/mkstr/mkstr.1 | 131 + usr.bin/mkstr/mkstr.c | 320 +++ usr.bin/newgrp/grutil.c | 338 +++ usr.bin/newgrp/grutil.h | 40 + usr.bin/newgrp/newgrp.1 | 121 + usr.bin/newgrp/newgrp.c | 195 ++ usr.bin/nice/nice.1 | 120 + usr.bin/nice/nice.c | 123 + usr.bin/nl/nl.1 | 214 ++ usr.bin/nl/nl.c | 406 +++ usr.bin/nohup/nohup.1 | 115 + usr.bin/nohup/nohup.c | 142 + usr.bin/paste/paste.1 | 116 + usr.bin/paste/paste.c | 233 ++ usr.bin/patch/backupfile.c | 254 ++ usr.bin/patch/backupfile.h | 42 + usr.bin/patch/common.h | 123 + usr.bin/patch/inp.c | 516 ++++ usr.bin/patch/inp.h | 35 + usr.bin/patch/mkpath.c | 84 + usr.bin/patch/patch.1 | 663 +++++ usr.bin/patch/patch.c | 1064 +++++++ usr.bin/patch/pathnames.h | 14 + usr.bin/patch/pch.c | 1600 +++++++++++ usr.bin/patch/pch.h | 59 + usr.bin/patch/util.c | 447 +++ usr.bin/patch/util.h | 51 + usr.bin/pathchk/pathchk.1 | 123 + usr.bin/pathchk/pathchk.c | 191 ++ usr.bin/pr/egetopt.c | 215 ++ usr.bin/pr/extern.h | 42 + usr.bin/pr/pr.1 | 361 +++ usr.bin/pr/pr.c | 1901 +++++++++++++ usr.bin/pr/pr.h | 79 + usr.bin/printf/printf.1 | 438 +++ usr.bin/printf/printf.c | 709 +++++ usr.bin/renice/renice.8 | 151 + usr.bin/renice/renice.c | 181 ++ usr.bin/sed/POSIX | 205 ++ usr.bin/sed/TEST/hanoi.sed | 103 + usr.bin/sed/TEST/math.sed | 164 ++ usr.bin/sed/TEST/sed.test | 554 ++++ usr.bin/sed/compile.c | 946 +++++++ usr.bin/sed/defs.h | 150 + usr.bin/sed/extern.h | 61 + usr.bin/sed/main.c | 517 ++++ usr.bin/sed/misc.c | 119 + usr.bin/sed/process.c | 792 ++++++ usr.bin/sed/sed.1 | 640 +++++ usr.bin/sort/append.c | 94 + usr.bin/sort/fields.c | 380 +++ usr.bin/sort/files.c | 279 ++ usr.bin/sort/fsort.c | 202 ++ usr.bin/sort/fsort.h | 78 + usr.bin/sort/init.c | 447 +++ usr.bin/sort/msort.c | 431 +++ usr.bin/sort/pathnames.h | 66 + usr.bin/sort/radix_sort.c | 217 ++ usr.bin/sort/sort.1 | 525 ++++ usr.bin/sort/sort.c | 419 +++ usr.bin/sort/sort.h | 201 ++ usr.bin/sort/tmp.c | 106 + usr.bin/split/split.1 | 132 + usr.bin/split/split.c | 362 +++ usr.bin/tabs/tabs.1 | 166 ++ usr.bin/tabs/tabs.c | 232 ++ usr.bin/tail/extern.h | 52 + usr.bin/tail/forward.c | 355 +++ usr.bin/tail/misc.c | 89 + usr.bin/tail/read.c | 210 ++ usr.bin/tail/reverse.c | 269 ++ usr.bin/tail/tac.1 | 56 + usr.bin/tail/tail.1 | 202 ++ usr.bin/tail/tail.c | 323 +++ usr.bin/talk/ctl.c | 125 + usr.bin/talk/ctl_transact.c | 108 + usr.bin/talk/display.c | 195 ++ usr.bin/talk/get_addrs.c | 81 + usr.bin/talk/get_names.c | 124 + usr.bin/talk/init_disp.c | 153 + usr.bin/talk/invite.c | 190 ++ usr.bin/talk/io.c | 139 + usr.bin/talk/look_up.c | 119 + usr.bin/talk/msgs.c | 82 + usr.bin/talk/talk.1 | 143 + usr.bin/talk/talk.c | 79 + usr.bin/talk/talk.h | 89 + usr.bin/talk/talk_ctl.h | 41 + usr.bin/tee/tee.1 | 84 + usr.bin/tee/tee.c | 149 + usr.bin/time/ext.h | 5 + usr.bin/time/time.1 | 220 ++ usr.bin/time/time.c | 215 ++ usr.bin/time/xtime.c | 9 + usr.bin/touch/touch.1 | 215 ++ usr.bin/touch/touch.c | 337 +++ usr.bin/tput/clear.sh | 38 + usr.bin/tput/tput.1 | 139 + usr.bin/tput/tput.c | 195 ++ usr.bin/tr/extern.h | 43 + usr.bin/tr/str.c | 453 +++ usr.bin/tr/tr.1 | 352 +++ usr.bin/tr/tr.c | 283 ++ usr.bin/true/true.1 | 56 + usr.bin/true/true.sh | 2 + usr.bin/tsort/tsort.1 | 87 + usr.bin/tsort/tsort.c | 433 +++ usr.bin/tty/tty.1 | 75 + usr.bin/tty/tty.c | 81 + usr.bin/uname/uname.1 | 86 + usr.bin/uname/uname.c | 159 ++ usr.bin/unexpand/unexpand.c | 218 ++ usr.bin/unifdef/unifdef.1 | 347 +++ usr.bin/unifdef/unifdef.c | 1037 +++++++ usr.bin/unifdef/unifdefall.sh | 29 + usr.bin/uniq/uniq.1 | 130 + usr.bin/uniq/uniq.c | 266 ++ usr.bin/uudecode/uudecode.c | 323 +++ usr.bin/uuencode/uuencode.1 | 175 ++ usr.bin/uuencode/uuencode.5 | 145 + usr.bin/uuencode/uuencode.c | 202 ++ usr.bin/wc/wc.1 | 146 + usr.bin/wc/wc.c | 354 +++ usr.bin/what/what.1 | 82 + usr.bin/what/what.c | 127 + usr.bin/who/utmpentry.c | 329 +++ usr.bin/who/utmpentry.h | 76 + usr.bin/who/who.1 | 197 ++ usr.bin/who/who.c | 391 +++ usr.bin/write/term_chk.c | 137 + usr.bin/write/term_chk.h | 36 + usr.bin/write/write.1 | 106 + usr.bin/write/write.c | 292 ++ usr.bin/xargs/pathnames.h | 36 + usr.bin/xargs/strnsubst.c | 115 + usr.bin/xargs/xargs.1 | 387 +++ usr.bin/xargs/xargs.c | 652 +++++ usr.sbin/link/link.8 | 64 + usr.sbin/link/link.c | 71 + usr.sbin/unlink/unlink.8 | 58 + usr.sbin/unlink/unlink.c | 70 + 844 files changed, 192027 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 bin/cat/cat.1 create mode 100644 bin/cat/cat.c create mode 100644 bin/chgrp/chgrp.1 create mode 100644 bin/chgrp/chown.8 create mode 100644 bin/chgrp/chown.c create mode 100644 bin/chmod/chmod.1 create mode 100644 bin/chmod/chmod.c create mode 100644 bin/chown/chgrp.1 create mode 100644 bin/chown/chown.8 create mode 100644 bin/chown/chown.c create mode 100644 bin/cp/cp.1 create mode 100644 bin/cp/cp.c create mode 100644 bin/cp/extern.h create mode 100644 bin/cp/utils.c create mode 100644 bin/date/date.1 create mode 100644 bin/date/date.c create mode 100644 bin/date/extern.h create mode 100644 bin/date/netdate.c create mode 100644 bin/dd/args.c create mode 100644 bin/dd/conv.c create mode 100644 bin/dd/conv_tab.c create mode 100644 bin/dd/dd.1 create mode 100644 bin/dd/dd.c create mode 100644 bin/dd/dd.h create mode 100644 bin/dd/dd_hostops.c create mode 100644 bin/dd/dd_rumpops.c create mode 100644 bin/dd/extern.h create mode 100644 bin/dd/misc.c create mode 100644 bin/dd/position.c create mode 100644 bin/df/df.1 create mode 100644 bin/df/df.c create mode 100644 bin/echo/echo.1 create mode 100644 bin/echo/echo.c create mode 100644 bin/ed/POSIX create mode 100644 bin/ed/README create mode 100644 bin/ed/buf.c create mode 100644 bin/ed/cbc.c create mode 100644 bin/ed/ed.1 create mode 100644 bin/ed/ed.h create mode 100644 bin/ed/glbl.c create mode 100644 bin/ed/io.c create mode 100644 bin/ed/main.c create mode 100644 bin/ed/re.c create mode 100644 bin/ed/sub.c create mode 100644 bin/ed/test/=.err create mode 100644 bin/ed/test/Makefile create mode 100644 bin/ed/test/README create mode 100644 bin/ed/test/TODO create mode 100644 bin/ed/test/a.d create mode 100644 bin/ed/test/a.r create mode 100644 bin/ed/test/a.t create mode 100644 bin/ed/test/a1.err create mode 100644 bin/ed/test/a2.err create mode 100644 bin/ed/test/addr.d create mode 100644 bin/ed/test/addr.r create mode 100644 bin/ed/test/addr.t create mode 100644 bin/ed/test/addr1.err create mode 100644 bin/ed/test/addr2.err create mode 100644 bin/ed/test/ascii.d create mode 100644 bin/ed/test/ascii.r create mode 100644 bin/ed/test/ascii.t create mode 100644 bin/ed/test/bang1.d create mode 100644 bin/ed/test/bang1.err create mode 100644 bin/ed/test/bang1.r create mode 100644 bin/ed/test/bang1.t create mode 100644 bin/ed/test/bang2.err create mode 100644 bin/ed/test/c.d create mode 100644 bin/ed/test/c.r create mode 100644 bin/ed/test/c.t create mode 100644 bin/ed/test/c1.err create mode 100644 bin/ed/test/c2.err create mode 100755 bin/ed/test/ckscripts.sh create mode 100644 bin/ed/test/d.d create mode 100644 bin/ed/test/d.err create mode 100644 bin/ed/test/d.r create mode 100644 bin/ed/test/d.t create mode 100644 bin/ed/test/e1.d create mode 100644 bin/ed/test/e1.err create mode 100644 bin/ed/test/e1.r create mode 100644 bin/ed/test/e1.t create mode 100644 bin/ed/test/e2.d create mode 100644 bin/ed/test/e2.err create mode 100644 bin/ed/test/e2.r create mode 100644 bin/ed/test/e2.t create mode 100644 bin/ed/test/e3.d create mode 100644 bin/ed/test/e3.err create mode 100644 bin/ed/test/e3.r create mode 100644 bin/ed/test/e3.t create mode 100644 bin/ed/test/e4.d create mode 100644 bin/ed/test/e4.r create mode 100644 bin/ed/test/e4.t create mode 100644 bin/ed/test/f1.err create mode 100644 bin/ed/test/f2.err create mode 100644 bin/ed/test/g1.d create mode 100644 bin/ed/test/g1.err create mode 100644 bin/ed/test/g1.r create mode 100644 bin/ed/test/g1.t create mode 100644 bin/ed/test/g2.d create mode 100644 bin/ed/test/g2.err create mode 100644 bin/ed/test/g2.r create mode 100644 bin/ed/test/g2.t create mode 100644 bin/ed/test/g3.d create mode 100644 bin/ed/test/g3.err create mode 100644 bin/ed/test/g3.r create mode 100644 bin/ed/test/g3.t create mode 100644 bin/ed/test/g4.d create mode 100644 bin/ed/test/g4.r create mode 100644 bin/ed/test/g4.t create mode 100644 bin/ed/test/g5.d create mode 100644 bin/ed/test/g5.r create mode 100644 bin/ed/test/g5.t create mode 100644 bin/ed/test/h.err create mode 100644 bin/ed/test/i.d create mode 100644 bin/ed/test/i.r create mode 100644 bin/ed/test/i.t create mode 100644 bin/ed/test/i1.err create mode 100644 bin/ed/test/i2.err create mode 100644 bin/ed/test/i3.err create mode 100644 bin/ed/test/j.d create mode 100644 bin/ed/test/j.r create mode 100644 bin/ed/test/j.t create mode 100644 bin/ed/test/k.d create mode 100644 bin/ed/test/k.r create mode 100644 bin/ed/test/k.t create mode 100644 bin/ed/test/k1.err create mode 100644 bin/ed/test/k2.err create mode 100644 bin/ed/test/k3.err create mode 100644 bin/ed/test/k4.err create mode 100644 bin/ed/test/l.d create mode 100644 bin/ed/test/l.r create mode 100644 bin/ed/test/l.t create mode 100644 bin/ed/test/m.d create mode 100644 bin/ed/test/m.err create mode 100644 bin/ed/test/m.r create mode 100644 bin/ed/test/m.t create mode 100755 bin/ed/test/mkscripts.sh create mode 100644 bin/ed/test/n.d create mode 100644 bin/ed/test/n.r create mode 100644 bin/ed/test/n.t create mode 100644 bin/ed/test/nl.err create mode 100644 bin/ed/test/nl1.d create mode 100644 bin/ed/test/nl1.r create mode 100644 bin/ed/test/nl1.t create mode 100644 bin/ed/test/nl2.d create mode 100644 bin/ed/test/nl2.r create mode 100644 bin/ed/test/nl2.t create mode 100644 bin/ed/test/p.d create mode 100644 bin/ed/test/p.r create mode 100644 bin/ed/test/p.t create mode 100644 bin/ed/test/q.d create mode 100644 bin/ed/test/q.r create mode 100644 bin/ed/test/q.t create mode 100644 bin/ed/test/q1.err create mode 100644 bin/ed/test/r1.d create mode 100644 bin/ed/test/r1.err create mode 100644 bin/ed/test/r1.r create mode 100644 bin/ed/test/r1.t create mode 100644 bin/ed/test/r2.d create mode 100644 bin/ed/test/r2.err create mode 100644 bin/ed/test/r2.r create mode 100644 bin/ed/test/r2.t create mode 100644 bin/ed/test/r3.d create mode 100644 bin/ed/test/r3.r create mode 100644 bin/ed/test/r3.t create mode 100644 bin/ed/test/s1.d create mode 100644 bin/ed/test/s1.err create mode 100644 bin/ed/test/s1.r create mode 100644 bin/ed/test/s1.t create mode 100644 bin/ed/test/s10.err create mode 100644 bin/ed/test/s2.d create mode 100644 bin/ed/test/s2.err create mode 100644 bin/ed/test/s2.r create mode 100644 bin/ed/test/s2.t create mode 100644 bin/ed/test/s3.d create mode 100644 bin/ed/test/s3.err create mode 100644 bin/ed/test/s3.r create mode 100644 bin/ed/test/s3.t create mode 100644 bin/ed/test/s4.err create mode 100644 bin/ed/test/s5.err create mode 100644 bin/ed/test/s6.err create mode 100644 bin/ed/test/s7.err create mode 100644 bin/ed/test/s8.err create mode 100644 bin/ed/test/s9.err create mode 100644 bin/ed/test/t.d create mode 100644 bin/ed/test/t.r create mode 100644 bin/ed/test/t1.d create mode 100644 bin/ed/test/t1.err create mode 100644 bin/ed/test/t1.r create mode 100644 bin/ed/test/t1.t create mode 100644 bin/ed/test/t2.d create mode 100644 bin/ed/test/t2.err create mode 100644 bin/ed/test/t2.r create mode 100644 bin/ed/test/t2.t create mode 100644 bin/ed/test/u.d create mode 100644 bin/ed/test/u.err create mode 100644 bin/ed/test/u.r create mode 100644 bin/ed/test/u.t create mode 100644 bin/ed/test/v.d create mode 100644 bin/ed/test/v.r create mode 100644 bin/ed/test/v.t create mode 100644 bin/ed/test/w.d create mode 100644 bin/ed/test/w.r create mode 100644 bin/ed/test/w.t create mode 100644 bin/ed/test/w1.err create mode 100644 bin/ed/test/w2.err create mode 100644 bin/ed/test/w3.err create mode 100644 bin/ed/test/x.err create mode 100644 bin/ed/test/z.err create mode 100644 bin/ed/undo.c create mode 100644 bin/expr/expr.1 create mode 100644 bin/expr/expr.y create mode 100644 bin/kill/kill.1 create mode 100644 bin/kill/kill.c create mode 100644 bin/ln/ln.1 create mode 100644 bin/ln/ln.c create mode 100644 bin/ls/cmp.c create mode 100644 bin/ls/extern.h create mode 100644 bin/ls/ls.1 create mode 100644 bin/ls/ls.c create mode 100644 bin/ls/ls.h create mode 100644 bin/ls/main.c create mode 100644 bin/ls/print.c create mode 100644 bin/ls/util.c create mode 100644 bin/mkdir/mkdir.1 create mode 100644 bin/mkdir/mkdir.c create mode 100644 bin/mv/mv.1 create mode 100644 bin/mv/mv.c create mode 100644 bin/mv/pathnames.h create mode 100644 bin/pax/ar_io.c create mode 100644 bin/pax/ar_subs.c create mode 100644 bin/pax/buf_subs.c create mode 100644 bin/pax/cpio.1 create mode 100644 bin/pax/cpio.c create mode 100644 bin/pax/cpio.h create mode 100644 bin/pax/dumptar.c create mode 100644 bin/pax/extern.h create mode 100644 bin/pax/file_subs.c create mode 100644 bin/pax/ftree.c create mode 100644 bin/pax/ftree.h create mode 100644 bin/pax/gen_subs.c create mode 100644 bin/pax/getoldopt.c create mode 100644 bin/pax/options.c create mode 100644 bin/pax/options.h create mode 100644 bin/pax/pat_rep.c create mode 100644 bin/pax/pat_rep.h create mode 100644 bin/pax/pax.1 create mode 100644 bin/pax/pax.c create mode 100644 bin/pax/pax.h create mode 100644 bin/pax/sel_subs.c create mode 100644 bin/pax/sel_subs.h create mode 100644 bin/pax/tables.c create mode 100644 bin/pax/tables.h create mode 100644 bin/pax/tar.1 create mode 100644 bin/pax/tar.c create mode 100644 bin/pax/tar.h create mode 100644 bin/pax/tty_subs.c create mode 100644 bin/ps/extern.h create mode 100644 bin/ps/fmt.c create mode 100644 bin/ps/keyword.c create mode 100644 bin/ps/nlist.c create mode 100644 bin/ps/print.c create mode 100644 bin/ps/ps.1 create mode 100644 bin/ps/ps.c create mode 100644 bin/ps/ps.h create mode 100644 bin/pwd/pwd.1 create mode 100644 bin/pwd/pwd.c create mode 100644 bin/rm/rm.1 create mode 100644 bin/rm/rm.c create mode 100644 bin/rmdir/rmdir.1 create mode 100644 bin/rmdir/rmdir.c create mode 100644 bin/sh/TOUR create mode 100644 bin/sh/USD.doc/Makefile create mode 100644 bin/sh/USD.doc/Rv7man create mode 100644 bin/sh/USD.doc/referargs create mode 100644 bin/sh/USD.doc/t.mac create mode 100644 bin/sh/USD.doc/t1 create mode 100644 bin/sh/USD.doc/t2 create mode 100644 bin/sh/USD.doc/t3 create mode 100644 bin/sh/USD.doc/t4 create mode 100644 bin/sh/alias.c create mode 100644 bin/sh/alias.h create mode 100644 bin/sh/arith_token.c create mode 100644 bin/sh/arith_tokens.h create mode 100644 bin/sh/arithmetic.c create mode 100644 bin/sh/arithmetic.h create mode 100644 bin/sh/bltin/bltin.h create mode 100644 bin/sh/bltin/echo.1 create mode 100644 bin/sh/bltin/echo.c create mode 100644 bin/sh/builtins.def create mode 100644 bin/sh/cd.c create mode 100644 bin/sh/cd.h create mode 100644 bin/sh/error.c create mode 100644 bin/sh/error.h create mode 100644 bin/sh/eval.c create mode 100644 bin/sh/eval.h create mode 100644 bin/sh/exec.c create mode 100644 bin/sh/exec.h create mode 100644 bin/sh/expand.c create mode 100644 bin/sh/expand.h create mode 100644 bin/sh/funcs/cmv create mode 100644 bin/sh/funcs/dirs create mode 100644 bin/sh/funcs/kill create mode 100644 bin/sh/funcs/login create mode 100644 bin/sh/funcs/newgrp create mode 100644 bin/sh/funcs/popd create mode 100644 bin/sh/funcs/pushd create mode 100644 bin/sh/funcs/suspend create mode 100644 bin/sh/histedit.c create mode 100644 bin/sh/init.h create mode 100644 bin/sh/input.c create mode 100644 bin/sh/input.h create mode 100644 bin/sh/jobs.c create mode 100644 bin/sh/jobs.h create mode 100644 bin/sh/machdep.h create mode 100644 bin/sh/mail.c create mode 100644 bin/sh/mail.h create mode 100644 bin/sh/main.c create mode 100644 bin/sh/main.h create mode 100644 bin/sh/memalloc.c create mode 100644 bin/sh/memalloc.h create mode 100644 bin/sh/miscbltin.c create mode 100644 bin/sh/miscbltin.h create mode 100644 bin/sh/mkbuiltins create mode 100755 bin/sh/mkinit.sh create mode 100755 bin/sh/mknodenames.sh create mode 100755 bin/sh/mknodes.sh create mode 100644 bin/sh/mkoptions.sh create mode 100644 bin/sh/mktokens create mode 100644 bin/sh/myhistedit.h create mode 100644 bin/sh/mystring.c create mode 100644 bin/sh/mystring.h create mode 100644 bin/sh/nodes.c.pat create mode 100644 bin/sh/nodetypes create mode 100644 bin/sh/option.list create mode 100644 bin/sh/options.c create mode 100644 bin/sh/options.h create mode 100644 bin/sh/output.c create mode 100644 bin/sh/output.h create mode 100644 bin/sh/parser.c create mode 100644 bin/sh/parser.h create mode 100644 bin/sh/redir.c create mode 100644 bin/sh/redir.h create mode 100644 bin/sh/sh.1 create mode 100644 bin/sh/shell.h create mode 100644 bin/sh/show.c create mode 100644 bin/sh/show.h create mode 100644 bin/sh/syntax.c create mode 100644 bin/sh/syntax.h create mode 100644 bin/sh/trap.c create mode 100644 bin/sh/trap.h create mode 100644 bin/sh/var.c create mode 100644 bin/sh/var.h create mode 100644 bin/sh/version.h create mode 100644 bin/sleep/sleep.1 create mode 100644 bin/sleep/sleep.c create mode 100644 bin/stty/cchar.c create mode 100644 bin/stty/extern.h create mode 100644 bin/stty/gfmt.c create mode 100644 bin/stty/key.c create mode 100644 bin/stty/modes.c create mode 100644 bin/stty/print.c create mode 100644 bin/stty/stty.1 create mode 100644 bin/stty/stty.c create mode 100644 bin/stty/stty.h create mode 100644 bin/test/TEST.csh create mode 100644 bin/test/test.1 create mode 100644 bin/test/test.c create mode 100644 usr.bin/asa/asa.1 create mode 100644 usr.bin/asa/asa.c create mode 100644 usr.bin/at/at.1 create mode 100644 usr.bin/at/at.c create mode 100644 usr.bin/at/at.h create mode 100644 usr.bin/at/panic.c create mode 100644 usr.bin/at/panic.h create mode 100644 usr.bin/at/parsetime.c create mode 100644 usr.bin/at/parsetime.h create mode 100644 usr.bin/at/pathnames.h create mode 100644 usr.bin/at/perm.c create mode 100644 usr.bin/at/perm.h create mode 100644 usr.bin/at/privs.c create mode 100644 usr.bin/at/privs.h create mode 100644 usr.bin/at/stime.c create mode 100644 usr.bin/at/stime.h create mode 100644 usr.bin/basename/basename.1 create mode 100644 usr.bin/basename/basename.c create mode 100644 usr.bin/c99/c99.1 create mode 100644 usr.bin/c99/c99.sh create mode 100644 usr.bin/cal/README create mode 100644 usr.bin/cal/cal.1 create mode 100644 usr.bin/cal/cal.c create mode 100644 usr.bin/cksum/cksum.1 create mode 100644 usr.bin/cksum/cksum.c create mode 100644 usr.bin/cksum/crc.c create mode 100644 usr.bin/cksum/crc_extern.h create mode 100644 usr.bin/cksum/extern.h create mode 100644 usr.bin/cksum/md2.c create mode 100644 usr.bin/cksum/md4.c create mode 100644 usr.bin/cksum/md5.c create mode 100644 usr.bin/cksum/print.c create mode 100644 usr.bin/cksum/rmd160.c create mode 100644 usr.bin/cksum/sha1.c create mode 100644 usr.bin/cksum/sha256.c create mode 100644 usr.bin/cksum/sha384.c create mode 100644 usr.bin/cksum/sha512.c create mode 100644 usr.bin/cksum/sum1.c create mode 100644 usr.bin/cksum/sum2.c create mode 100644 usr.bin/cmp/cmp.1 create mode 100644 usr.bin/cmp/cmp.c create mode 100644 usr.bin/cmp/extern.h create mode 100644 usr.bin/cmp/misc.c create mode 100644 usr.bin/cmp/regular.c create mode 100644 usr.bin/cmp/special.c create mode 100644 usr.bin/comm/comm.1 create mode 100644 usr.bin/comm/comm.c create mode 100644 usr.bin/compress/compress.1 create mode 100644 usr.bin/compress/compress.c create mode 100644 usr.bin/compress/doc/NOTES create mode 100644 usr.bin/compress/doc/README create mode 100644 usr.bin/compress/doc/revision.log create mode 100644 usr.bin/compress/zopen.3 create mode 100644 usr.bin/compress/zopen.c create mode 100644 usr.bin/csplit/csplit.1 create mode 100644 usr.bin/csplit/csplit.c create mode 100644 usr.bin/ctags/C.c create mode 100644 usr.bin/ctags/ctags.1 create mode 100644 usr.bin/ctags/ctags.c create mode 100644 usr.bin/ctags/ctags.h create mode 100644 usr.bin/ctags/fortran.c create mode 100644 usr.bin/ctags/lisp.c create mode 100644 usr.bin/ctags/print.c create mode 100644 usr.bin/ctags/test/ctags.test create mode 100644 usr.bin/ctags/tree.c create mode 100644 usr.bin/ctags/yacc.c create mode 100644 usr.bin/cut/cut.1 create mode 100644 usr.bin/cut/cut.c create mode 100644 usr.bin/cut/x_cut.c create mode 100644 usr.bin/dirname/dirname.c create mode 100644 usr.bin/du/du.1 create mode 100644 usr.bin/du/du.c create mode 100644 usr.bin/env/env.1 create mode 100644 usr.bin/env/env.c create mode 100644 usr.bin/expand/expand.1 create mode 100644 usr.bin/expand/expand.c create mode 100644 usr.bin/false/false.1 create mode 100644 usr.bin/false/false.sh create mode 100644 usr.bin/find/extern.h create mode 100644 usr.bin/find/find.1 create mode 100644 usr.bin/find/find.c create mode 100644 usr.bin/find/find.h create mode 100644 usr.bin/find/function.c create mode 100644 usr.bin/find/ls.c create mode 100644 usr.bin/find/main.c create mode 100644 usr.bin/find/misc.c create mode 100644 usr.bin/find/operator.c create mode 100644 usr.bin/find/option.c create mode 100644 usr.bin/fold/fold.1 create mode 100644 usr.bin/fold/fold.c create mode 100644 usr.bin/gencat/gencat.1 create mode 100644 usr.bin/gencat/gencat.c create mode 100644 usr.bin/getconf/getconf.1 create mode 100644 usr.bin/getconf/getconf.c create mode 100644 usr.bin/grep/fastgrep.c create mode 100644 usr.bin/grep/file.c create mode 100644 usr.bin/grep/grep.1 create mode 100644 usr.bin/grep/grep.c create mode 100644 usr.bin/grep/grep.h create mode 100644 usr.bin/grep/nls/C.msg create mode 100644 usr.bin/grep/nls/es_ES.ISO8859-1.msg create mode 100644 usr.bin/grep/nls/gl_ES.ISO8859-1.msg create mode 100644 usr.bin/grep/nls/hu_HU.ISO8859-2.msg create mode 100644 usr.bin/grep/nls/ja_JP.SJIS.msg create mode 100644 usr.bin/grep/nls/ja_JP.UTF-8.msg create mode 100644 usr.bin/grep/nls/ja_JP.eucJP.msg create mode 100644 usr.bin/grep/nls/pt_BR.ISO8859-1.msg create mode 100644 usr.bin/grep/nls/ru_RU.KOI8-R.msg create mode 100644 usr.bin/grep/nls/uk_UA.UTF-8.msg create mode 100644 usr.bin/grep/nls/zh_CN.UTF-8.msg create mode 100644 usr.bin/grep/queue.c create mode 100644 usr.bin/grep/util.c create mode 100644 usr.bin/head/head.1 create mode 100644 usr.bin/head/head.c create mode 100644 usr.bin/iconv/iconv.1 create mode 100644 usr.bin/iconv/iconv.c create mode 100644 usr.bin/id/groups.1 create mode 100644 usr.bin/id/id.1 create mode 100644 usr.bin/id/id.c create mode 100644 usr.bin/id/whoami.1 create mode 100644 usr.bin/ipcrm/ipcrm.1 create mode 100644 usr.bin/ipcrm/ipcrm.c create mode 100644 usr.bin/ipcs/ipcs.1 create mode 100644 usr.bin/ipcs/ipcs.c create mode 100644 usr.bin/join/join.1 create mode 100644 usr.bin/join/join.c create mode 100644 usr.bin/ldd/Makefile.common create mode 100644 usr.bin/ldd/Makefile.elf create mode 100644 usr.bin/ldd/build/Makefile create mode 100644 usr.bin/ldd/dummy.c create mode 100644 usr.bin/ldd/elf32/Makefile create mode 100644 usr.bin/ldd/elf32_compat/Makefile create mode 100644 usr.bin/ldd/elf64/Makefile create mode 100644 usr.bin/ldd/ldd.1 create mode 100644 usr.bin/ldd/ldd.c create mode 100644 usr.bin/ldd/ldd.h create mode 100644 usr.bin/ldd/ldd_elfxx.c create mode 100644 usr.bin/locale/locale.1 create mode 100644 usr.bin/locale/locale.c create mode 100644 usr.bin/logger/logger.1 create mode 100644 usr.bin/logger/logger.c create mode 100644 usr.bin/logname/logname.1 create mode 100644 usr.bin/logname/logname.c create mode 100644 usr.bin/m4/NOTES create mode 100644 usr.bin/m4/PSD.doc/Makefile create mode 100644 usr.bin/m4/TEST/ack.m4 create mode 100644 usr.bin/m4/TEST/hanoi.m4 create mode 100644 usr.bin/m4/TEST/hash.m4 create mode 100644 usr.bin/m4/TEST/math.m4 create mode 100644 usr.bin/m4/TEST/sqroot.m4 create mode 100644 usr.bin/m4/TEST/string.m4 create mode 100644 usr.bin/m4/TEST/test.m4 create mode 100644 usr.bin/m4/eval.c create mode 100644 usr.bin/m4/expr.c create mode 100644 usr.bin/m4/extern.h create mode 100644 usr.bin/m4/gnum4.c create mode 100644 usr.bin/m4/lib/ohash.h create mode 100644 usr.bin/m4/lib/ohash_create_entry.c create mode 100644 usr.bin/m4/lib/ohash_delete.c create mode 100644 usr.bin/m4/lib/ohash_do.c create mode 100644 usr.bin/m4/lib/ohash_entries.c create mode 100644 usr.bin/m4/lib/ohash_enum.c create mode 100644 usr.bin/m4/lib/ohash_init.3 create mode 100644 usr.bin/m4/lib/ohash_init.c create mode 100644 usr.bin/m4/lib/ohash_int.h create mode 100644 usr.bin/m4/lib/ohash_interval.3 create mode 100644 usr.bin/m4/lib/ohash_interval.c create mode 100644 usr.bin/m4/lib/ohash_lookup_interval.c create mode 100644 usr.bin/m4/lib/ohash_lookup_memory.c create mode 100644 usr.bin/m4/lib/ohash_qlookup.c create mode 100644 usr.bin/m4/lib/ohash_qlookupi.c create mode 100644 usr.bin/m4/lib/strtonum.c create mode 100644 usr.bin/m4/look.c create mode 100644 usr.bin/m4/m4.1 create mode 100644 usr.bin/m4/main.c create mode 100644 usr.bin/m4/mdef.h create mode 100644 usr.bin/m4/misc.c create mode 100644 usr.bin/m4/parser.y create mode 100644 usr.bin/m4/pathnames.h create mode 100644 usr.bin/m4/stdd.h create mode 100644 usr.bin/m4/tokenizer.l create mode 100644 usr.bin/m4/trace.c create mode 100644 usr.bin/make/Makefile.boot create mode 100644 usr.bin/make/PSD.doc/Makefile create mode 100644 usr.bin/make/PSD.doc/tutorial.ms create mode 100644 usr.bin/make/arch.c create mode 100644 usr.bin/make/buf.c create mode 100644 usr.bin/make/buf.h create mode 100644 usr.bin/make/compat.c create mode 100644 usr.bin/make/cond.c create mode 100644 usr.bin/make/config.h create mode 100644 usr.bin/make/dir.c create mode 100644 usr.bin/make/dir.h create mode 100644 usr.bin/make/for.c create mode 100644 usr.bin/make/hash.c create mode 100644 usr.bin/make/hash.h create mode 100644 usr.bin/make/job.c create mode 100644 usr.bin/make/job.h create mode 100644 usr.bin/make/lst.h create mode 100644 usr.bin/make/lst.lib/Makefile create mode 100644 usr.bin/make/lst.lib/lstAppend.c create mode 100644 usr.bin/make/lst.lib/lstAtEnd.c create mode 100644 usr.bin/make/lst.lib/lstAtFront.c create mode 100644 usr.bin/make/lst.lib/lstClose.c create mode 100644 usr.bin/make/lst.lib/lstConcat.c create mode 100644 usr.bin/make/lst.lib/lstDatum.c create mode 100644 usr.bin/make/lst.lib/lstDeQueue.c create mode 100644 usr.bin/make/lst.lib/lstDestroy.c create mode 100644 usr.bin/make/lst.lib/lstDupl.c create mode 100644 usr.bin/make/lst.lib/lstEnQueue.c create mode 100644 usr.bin/make/lst.lib/lstFind.c create mode 100644 usr.bin/make/lst.lib/lstFindFrom.c create mode 100644 usr.bin/make/lst.lib/lstFirst.c create mode 100644 usr.bin/make/lst.lib/lstForEach.c create mode 100644 usr.bin/make/lst.lib/lstForEachFrom.c create mode 100644 usr.bin/make/lst.lib/lstInit.c create mode 100644 usr.bin/make/lst.lib/lstInsert.c create mode 100644 usr.bin/make/lst.lib/lstInt.h create mode 100644 usr.bin/make/lst.lib/lstIsAtEnd.c create mode 100644 usr.bin/make/lst.lib/lstIsEmpty.c create mode 100644 usr.bin/make/lst.lib/lstLast.c create mode 100644 usr.bin/make/lst.lib/lstMember.c create mode 100644 usr.bin/make/lst.lib/lstNext.c create mode 100644 usr.bin/make/lst.lib/lstOpen.c create mode 100644 usr.bin/make/lst.lib/lstPrev.c create mode 100644 usr.bin/make/lst.lib/lstRemove.c create mode 100644 usr.bin/make/lst.lib/lstReplace.c create mode 100644 usr.bin/make/lst.lib/lstSucc.c create mode 100644 usr.bin/make/main.c create mode 100644 usr.bin/make/make.1 create mode 100644 usr.bin/make/make.c create mode 100644 usr.bin/make/make.h create mode 100644 usr.bin/make/make_malloc.c create mode 100644 usr.bin/make/make_malloc.h create mode 100644 usr.bin/make/meta.c create mode 100644 usr.bin/make/meta.h create mode 100644 usr.bin/make/metachar.c create mode 100644 usr.bin/make/metachar.h create mode 100644 usr.bin/make/nonints.h create mode 100644 usr.bin/make/parse.c create mode 100644 usr.bin/make/pathnames.h create mode 100644 usr.bin/make/sprite.h create mode 100644 usr.bin/make/str.c create mode 100644 usr.bin/make/strlist.c create mode 100644 usr.bin/make/strlist.h create mode 100644 usr.bin/make/suff.c create mode 100644 usr.bin/make/targ.c create mode 100644 usr.bin/make/trace.c create mode 100644 usr.bin/make/trace.h create mode 100644 usr.bin/make/unit-tests/Makefile create mode 100644 usr.bin/make/unit-tests/comment.exp create mode 100644 usr.bin/make/unit-tests/comment.mk create mode 100644 usr.bin/make/unit-tests/cond1.exp create mode 100644 usr.bin/make/unit-tests/cond1.mk create mode 100644 usr.bin/make/unit-tests/cond2.exp create mode 100644 usr.bin/make/unit-tests/cond2.mk create mode 100644 usr.bin/make/unit-tests/doterror.exp create mode 100644 usr.bin/make/unit-tests/doterror.mk create mode 100644 usr.bin/make/unit-tests/dotwait.exp create mode 100644 usr.bin/make/unit-tests/dotwait.mk create mode 100644 usr.bin/make/unit-tests/error.exp create mode 100644 usr.bin/make/unit-tests/error.mk create mode 100644 usr.bin/make/unit-tests/escape.exp create mode 100644 usr.bin/make/unit-tests/escape.mk create mode 100644 usr.bin/make/unit-tests/export-all.exp create mode 100644 usr.bin/make/unit-tests/export-all.mk create mode 100644 usr.bin/make/unit-tests/export-env.exp create mode 100644 usr.bin/make/unit-tests/export-env.mk create mode 100644 usr.bin/make/unit-tests/export.exp create mode 100644 usr.bin/make/unit-tests/export.mk create mode 100644 usr.bin/make/unit-tests/forloop.exp create mode 100644 usr.bin/make/unit-tests/forloop.mk create mode 100644 usr.bin/make/unit-tests/forsubst.exp create mode 100644 usr.bin/make/unit-tests/forsubst.mk create mode 100644 usr.bin/make/unit-tests/hash.exp create mode 100644 usr.bin/make/unit-tests/hash.mk create mode 100644 usr.bin/make/unit-tests/impsrc.exp create mode 100644 usr.bin/make/unit-tests/impsrc.mk create mode 100644 usr.bin/make/unit-tests/misc.exp create mode 100644 usr.bin/make/unit-tests/misc.mk create mode 100644 usr.bin/make/unit-tests/moderrs.exp create mode 100644 usr.bin/make/unit-tests/moderrs.mk create mode 100644 usr.bin/make/unit-tests/modmatch.exp create mode 100644 usr.bin/make/unit-tests/modmatch.mk create mode 100644 usr.bin/make/unit-tests/modmisc.exp create mode 100644 usr.bin/make/unit-tests/modmisc.mk create mode 100644 usr.bin/make/unit-tests/modorder.exp create mode 100644 usr.bin/make/unit-tests/modorder.mk create mode 100644 usr.bin/make/unit-tests/modts.exp create mode 100644 usr.bin/make/unit-tests/modts.mk create mode 100644 usr.bin/make/unit-tests/modword.exp create mode 100644 usr.bin/make/unit-tests/modword.mk create mode 100644 usr.bin/make/unit-tests/order.exp create mode 100644 usr.bin/make/unit-tests/order.mk create mode 100644 usr.bin/make/unit-tests/phony-end.exp create mode 100644 usr.bin/make/unit-tests/phony-end.mk create mode 100644 usr.bin/make/unit-tests/posix.exp create mode 100644 usr.bin/make/unit-tests/posix.mk create mode 100644 usr.bin/make/unit-tests/posix1.exp create mode 100644 usr.bin/make/unit-tests/posix1.mk create mode 100644 usr.bin/make/unit-tests/qequals.exp create mode 100644 usr.bin/make/unit-tests/qequals.mk create mode 100644 usr.bin/make/unit-tests/suffixes.exp create mode 100644 usr.bin/make/unit-tests/suffixes.mk create mode 100644 usr.bin/make/unit-tests/sunshcmd.exp create mode 100644 usr.bin/make/unit-tests/sunshcmd.mk create mode 100644 usr.bin/make/unit-tests/sysv.exp create mode 100644 usr.bin/make/unit-tests/sysv.mk create mode 100644 usr.bin/make/unit-tests/ternary.exp create mode 100644 usr.bin/make/unit-tests/ternary.mk create mode 100644 usr.bin/make/unit-tests/unexport-env.exp create mode 100644 usr.bin/make/unit-tests/unexport-env.mk create mode 100644 usr.bin/make/unit-tests/unexport.exp create mode 100644 usr.bin/make/unit-tests/unexport.mk create mode 100644 usr.bin/make/unit-tests/varcmd.exp create mode 100644 usr.bin/make/unit-tests/varcmd.mk create mode 100644 usr.bin/make/unit-tests/varmisc.exp create mode 100644 usr.bin/make/unit-tests/varmisc.mk create mode 100644 usr.bin/make/unit-tests/varquote.exp create mode 100644 usr.bin/make/unit-tests/varquote.mk create mode 100644 usr.bin/make/unit-tests/varshell.exp create mode 100644 usr.bin/make/unit-tests/varshell.mk create mode 100644 usr.bin/make/util.c create mode 100644 usr.bin/make/var.c create mode 100644 usr.bin/man/man.1 create mode 100644 usr.bin/man/man.c create mode 100644 usr.bin/man/man.conf.5 create mode 100644 usr.bin/man/manconf.c create mode 100644 usr.bin/man/manconf.h create mode 100644 usr.bin/man/pathnames.h create mode 100644 usr.bin/mesg/mesg.1 create mode 100644 usr.bin/mesg/mesg.c create mode 100644 usr.bin/mkfifo/mkfifo.1 create mode 100644 usr.bin/mkfifo/mkfifo.c create mode 100644 usr.bin/mkstr/mkstr.1 create mode 100644 usr.bin/mkstr/mkstr.c create mode 100644 usr.bin/newgrp/grutil.c create mode 100644 usr.bin/newgrp/grutil.h create mode 100644 usr.bin/newgrp/newgrp.1 create mode 100644 usr.bin/newgrp/newgrp.c create mode 100644 usr.bin/nice/nice.1 create mode 100644 usr.bin/nice/nice.c create mode 100644 usr.bin/nl/nl.1 create mode 100644 usr.bin/nl/nl.c create mode 100644 usr.bin/nohup/nohup.1 create mode 100644 usr.bin/nohup/nohup.c create mode 100644 usr.bin/paste/paste.1 create mode 100644 usr.bin/paste/paste.c create mode 100644 usr.bin/patch/backupfile.c create mode 100644 usr.bin/patch/backupfile.h create mode 100644 usr.bin/patch/common.h create mode 100644 usr.bin/patch/inp.c create mode 100644 usr.bin/patch/inp.h create mode 100644 usr.bin/patch/mkpath.c create mode 100644 usr.bin/patch/patch.1 create mode 100644 usr.bin/patch/patch.c create mode 100644 usr.bin/patch/pathnames.h create mode 100644 usr.bin/patch/pch.c create mode 100644 usr.bin/patch/pch.h create mode 100644 usr.bin/patch/util.c create mode 100644 usr.bin/patch/util.h create mode 100644 usr.bin/pathchk/pathchk.1 create mode 100644 usr.bin/pathchk/pathchk.c create mode 100644 usr.bin/pr/egetopt.c create mode 100644 usr.bin/pr/extern.h create mode 100644 usr.bin/pr/pr.1 create mode 100644 usr.bin/pr/pr.c create mode 100644 usr.bin/pr/pr.h create mode 100644 usr.bin/printf/printf.1 create mode 100644 usr.bin/printf/printf.c create mode 100644 usr.bin/renice/renice.8 create mode 100644 usr.bin/renice/renice.c create mode 100644 usr.bin/sed/POSIX create mode 100644 usr.bin/sed/TEST/hanoi.sed create mode 100644 usr.bin/sed/TEST/math.sed create mode 100644 usr.bin/sed/TEST/sed.test create mode 100644 usr.bin/sed/compile.c create mode 100644 usr.bin/sed/defs.h create mode 100644 usr.bin/sed/extern.h create mode 100644 usr.bin/sed/main.c create mode 100644 usr.bin/sed/misc.c create mode 100644 usr.bin/sed/process.c create mode 100644 usr.bin/sed/sed.1 create mode 100644 usr.bin/sort/append.c create mode 100644 usr.bin/sort/fields.c create mode 100644 usr.bin/sort/files.c create mode 100644 usr.bin/sort/fsort.c create mode 100644 usr.bin/sort/fsort.h create mode 100644 usr.bin/sort/init.c create mode 100644 usr.bin/sort/msort.c create mode 100644 usr.bin/sort/pathnames.h create mode 100644 usr.bin/sort/radix_sort.c create mode 100644 usr.bin/sort/sort.1 create mode 100644 usr.bin/sort/sort.c create mode 100644 usr.bin/sort/sort.h create mode 100644 usr.bin/sort/tmp.c create mode 100644 usr.bin/split/split.1 create mode 100644 usr.bin/split/split.c create mode 100644 usr.bin/tabs/tabs.1 create mode 100644 usr.bin/tabs/tabs.c create mode 100644 usr.bin/tail/extern.h create mode 100644 usr.bin/tail/forward.c create mode 100644 usr.bin/tail/misc.c create mode 100644 usr.bin/tail/read.c create mode 100644 usr.bin/tail/reverse.c create mode 100644 usr.bin/tail/tac.1 create mode 100644 usr.bin/tail/tail.1 create mode 100644 usr.bin/tail/tail.c create mode 100644 usr.bin/talk/ctl.c create mode 100644 usr.bin/talk/ctl_transact.c create mode 100644 usr.bin/talk/display.c create mode 100644 usr.bin/talk/get_addrs.c create mode 100644 usr.bin/talk/get_names.c create mode 100644 usr.bin/talk/init_disp.c create mode 100644 usr.bin/talk/invite.c create mode 100644 usr.bin/talk/io.c create mode 100644 usr.bin/talk/look_up.c create mode 100644 usr.bin/talk/msgs.c create mode 100644 usr.bin/talk/talk.1 create mode 100644 usr.bin/talk/talk.c create mode 100644 usr.bin/talk/talk.h create mode 100644 usr.bin/talk/talk_ctl.h create mode 100644 usr.bin/tee/tee.1 create mode 100644 usr.bin/tee/tee.c create mode 100644 usr.bin/time/ext.h create mode 100644 usr.bin/time/time.1 create mode 100644 usr.bin/time/time.c create mode 100644 usr.bin/time/xtime.c create mode 100644 usr.bin/touch/touch.1 create mode 100644 usr.bin/touch/touch.c create mode 100644 usr.bin/tput/clear.sh create mode 100644 usr.bin/tput/tput.1 create mode 100644 usr.bin/tput/tput.c create mode 100644 usr.bin/tr/extern.h create mode 100644 usr.bin/tr/str.c create mode 100644 usr.bin/tr/tr.1 create mode 100644 usr.bin/tr/tr.c create mode 100644 usr.bin/true/true.1 create mode 100644 usr.bin/true/true.sh create mode 100644 usr.bin/tsort/tsort.1 create mode 100644 usr.bin/tsort/tsort.c create mode 100644 usr.bin/tty/tty.1 create mode 100644 usr.bin/tty/tty.c create mode 100644 usr.bin/uname/uname.1 create mode 100644 usr.bin/uname/uname.c create mode 100644 usr.bin/unexpand/unexpand.c create mode 100644 usr.bin/unifdef/unifdef.1 create mode 100644 usr.bin/unifdef/unifdef.c create mode 100644 usr.bin/unifdef/unifdefall.sh create mode 100644 usr.bin/uniq/uniq.1 create mode 100644 usr.bin/uniq/uniq.c create mode 100644 usr.bin/uudecode/uudecode.c create mode 100644 usr.bin/uuencode/uuencode.1 create mode 100644 usr.bin/uuencode/uuencode.5 create mode 100644 usr.bin/uuencode/uuencode.c create mode 100644 usr.bin/wc/wc.1 create mode 100644 usr.bin/wc/wc.c create mode 100644 usr.bin/what/what.1 create mode 100644 usr.bin/what/what.c create mode 100644 usr.bin/who/utmpentry.c create mode 100644 usr.bin/who/utmpentry.h create mode 100644 usr.bin/who/who.1 create mode 100644 usr.bin/who/who.c create mode 100644 usr.bin/write/term_chk.c create mode 100644 usr.bin/write/term_chk.h create mode 100644 usr.bin/write/write.1 create mode 100644 usr.bin/write/write.c create mode 100644 usr.bin/xargs/pathnames.h create mode 100644 usr.bin/xargs/strnsubst.c create mode 100644 usr.bin/xargs/xargs.1 create mode 100644 usr.bin/xargs/xargs.c create mode 100644 usr.sbin/link/link.8 create mode 100644 usr.sbin/link/link.c create mode 100644 usr.sbin/unlink/unlink.8 create mode 100644 usr.sbin/unlink/unlink.c diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc465c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2019 Adélie Userland Team. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ec406f1 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Userland + +This project is a port of NetBSD userland (`bin`, `sbin`, `usr.bin`, and `usr.sbin` in the NetBSD `src` distribution) to the musl implementation of the C standard library. It aims to provide a complete POSIX userland, with support for user-friendly extensions. + +# Rationale + +There are a multitude of other projects which aim to provide a Linux userland. None of them are POSIX-compliant, and many have no interest in becoming 100% POSIX-compliant. Some of those projects additionally do not wish to support extensions provided by other projects. + +# License + +All code in this repository is licensed under `BSD-3-Clause`; see `LICENSE` for a copy of the license text. + +# Contributing + +Contributions may be made in several ways: + +* Write code. +* Write manual pages. +* Write ancillary documentation. +* File tickets in the issue tracker for features which are missing but implemented by another project. +* Participate constructively in project discussions. + diff --git a/bin/cat/cat.1 b/bin/cat/cat.1 new file mode 100644 index 0000000..f27ce59 --- /dev/null +++ b/bin/cat/cat.1 @@ -0,0 +1,217 @@ +.\" $NetBSD: cat.1,v 1.41 2017/10/02 08:24:17 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)cat.1 8.3 (Berkeley) 5/2/95 +.\" +.Dd June 15, 2014 +.Dt CAT 1 +.Os +.Sh NAME +.Nm cat +.Nd concatenate and print files +.Sh SYNOPSIS +.Nm +.Op Fl beflnstuv +.Op Fl B Ar bsize +.Op Fl +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility reads files sequentially, writing them to the standard output. +The +.Ar file +operands are processed in command line order. +A single dash represents the standard input, +and may appear multiple times in the +.Ar file +list. +If no +.Ar file +operands are given, standard input is read. +.Pp +The word +.Dq concatenate +is just a verbose synonym for +.Dq catenate . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl B Ar bsize +Read with a buffer size of +.Ar bsize +bytes, instead of the default buffer size which is the blocksize of the +output file. +.It Fl b +Implies the +.Fl n +option, but doesn't number blank lines. +.It Fl e +Implies the +.Fl v +option, and displays a dollar sign +.Pq Ql \&$ +at the end of each line +as well. +.It Fl f +Only attempt to display regular files. +.It Fl l +Set an exclusive advisory lock on the standard output file descriptor. +This lock is set using +.Xr fcntl 2 +with the +.Dv F_SETLKW +command. +If the output file is already locked, +.Nm +will block until the lock is acquired. +.It Fl n +Number the output lines, starting at 1. +.It Fl s +Squeeze multiple adjacent empty lines, causing the output to be +single spaced. +.It Fl t +Implies the +.Fl v +option, and displays tab characters as +.Ql ^I +as well. +.It Fl u +The +.Fl u +option guarantees that the output is unbuffered. +.It Fl v +Displays non-printing characters so they are visible. +Control characters print as +.Ql ^X +for control-X; the delete +character (octal 0177) prints as +.Ql ^? . +Non-ascii characters (with the high bit set) are printed as +.Ql M- +(for meta) followed by the character for the low 7 bits. +.El +.Sh EXIT STATUS +.Ex -std cat +.Sh EXAMPLES +The command: +.Bd -literal -offset indent +.Ic cat file1 +.Ed +.Pp +will print the contents of +.Ar file1 +to the standard output. +.Pp +The command: +.Bd -literal -offset indent +.Ic cat file1 file2 > file3 +.Ed +.Pp +will sequentially print the contents of +.Ar file1 +and +.Ar file2 +to the file +.Ar file3 , +truncating +.Ar file3 +if it already exists. +See the manual page for your shell (e.g., +.Xr sh 1 ) +for more information on redirection. +.Pp +The command: +.Bd -literal -offset indent +.Ic cat file1 - file2 - file3 +.Ed +.Pp +will print the contents of +.Ar file1 , +print data it receives from the standard input until it receives an +.Dv EOF +.Pq Sq ^D +character, print the contents of +.Ar file2 , +read and output contents of the standard input again, then finally output +the contents of +.Ar file3 . +Note that if the standard input referred to a file, the second dash +on the command-line would have no effect, since the entire contents of the file +would have already been read and printed by +.Nm +when it encountered the first +.Ql \&- +operand. +.Sh SEE ALSO +.Xr head 1 , +.Xr hexdump 1 , +.Xr lpr 1 , +.Xr more 1 , +.Xr pr 1 , +.Xr tac 1 , +.Xr tail 1 , +.Xr view 1 , +.Xr vis 1 , +.Xr fcntl 2 +.Rs +.%A Rob Pike +.%T "UNIX Style, or cat -v Considered Harmful" +.%J "USENIX Summer Conference Proceedings" +.%D 1983 +.Re +.Sh STANDARDS +The +.Nm +utility is expected to conform to the +.St -p1003.2-92 +specification. +.Pp +The flags +.Op Fl Bbeflnstv +are extensions to the specification. +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 . +Dennis Ritchie designed and wrote the first man page. +It appears to have been +.Xr cat 1 . +.Sh BUGS +Because of the shell language mechanism used to perform output +redirection, the command +.Dq Li cat file1 file2 > file1 +will cause the original data in file1 to be destroyed! +This is performed by the shell before +.Nm +is run. diff --git a/bin/cat/cat.c b/bin/cat/cat.c new file mode 100644 index 0000000..563ba63 --- /dev/null +++ b/bin/cat/cat.c @@ -0,0 +1,321 @@ +/* $NetBSD: cat.c,v 1.57 2016/06/16 00:52:37 sevan Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kevin Fall. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +__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 +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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//LC_TIME +Description of time locale . +.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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ": 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 +#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 +#include +#include + +#include +#include +#define TSPTYPES +#include + +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include + +#ifndef NO_IOFLAG +#include +#endif /* NO_IOFLAG */ +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include + +#include +#include +#include + +#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 +#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 + +/* + * 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 +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 +#ifndef lint +__RCSID("$NetBSD: dd_hostops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $"); +#endif /* !lint */ + +#include +#include + +#include +#include + +#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 +#ifndef lint +__RCSID("$NetBSD: dd_rumpops.c,v 1.1 2011/02/04 19:42:12 pooka Exp $"); +#endif /* !lint */ + +#include +#include + +#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 + +#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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#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 -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", , , + , , , + +.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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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", , , + * , , , + * + */ + (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 +#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 +#include +#include +#include + +/* 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 arguments are processed for backslash escapes, i.e., any + character preceded by a backslash is interpreted literally. If the + first unescaped character of a 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 < +#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 +#include + +#include +#include +#include + +#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 - < 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 +#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 +#include +#include +#include +#ifdef DES +#include +#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 +#if defined(BSD) && BSD >= 199103 || defined(__386BSD__) +# include /* for MAXPATHLEN */ +#endif +#include +#if defined(sun) || defined(__NetBSD__) || defined(__APPLE__) +# include +#endif +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include + +#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 +#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 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 +#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 +#include +#include +#include +#include +#include + +#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 +#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 +#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 +#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 + +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 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 Binary files /dev/null and b/bin/ed/test/ascii.d differ diff --git a/bin/ed/test/ascii.r b/bin/ed/test/ascii.r new file mode 100644 index 0000000..c866266 Binary files /dev/null and b/bin/ed/test/ascii.r differ diff --git a/bin/ed/test/ascii.t b/bin/ed/test/ascii.t new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/bang1.d b/bin/ed/test/bang1.d new file mode 100644 index 0000000..e69de29 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 diff --git a/bin/ed/test/l.r b/bin/ed/test/l.r new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/l.t b/bin/ed/test/l.t new file mode 100644 index 0000000..e69de29 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 diff --git a/bin/ed/test/n.r b/bin/ed/test/n.r new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/n.t b/bin/ed/test/n.t new file mode 100644 index 0000000..e69de29 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 diff --git a/bin/ed/test/p.r b/bin/ed/test/p.r new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/p.t b/bin/ed/test/p.t new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/q.d b/bin/ed/test/q.d new file mode 100644 index 0000000..e69de29 diff --git a/bin/ed/test/q.r b/bin/ed/test/q.r new file mode 100644 index 0000000..e69de29 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 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 +#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 and Jaromir Dolecek . +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.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 and J.T. Conklin . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +%{ +#include +#ifndef lint +__RCSID("$NetBSD: expr.y,v 1.45 2018/06/27 17:23:36 kamil Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include + +#include +#include + +#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 +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#ifndef lint +__RCSID("$NetBSD: main.c,v 1.5 2016/09/05 01:00:07 sevan Exp $"); +#endif /* not lint */ + +#include +#include + +#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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#ifdef HAVE_SYS_MTIO_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORT_RMT +#define __RMTLIB_PRIVATE +#include +#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[] = ""; /* pseudo name for stdout */ +static char STDN[] = ""; /* pseudo name for stdin */ +static char 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 \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 +#include + +/* + * 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#endif +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#if HAVE_NBTOOL_CONFIG_H +#include "compat_getopt.h" +#else +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#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 >> \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 +/* + * 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + * 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 >> +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 +#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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pax.h" +#include "extern.h" +#include + +/* + * 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 +__RCSID("$NetBSD: fmt.c,v 1.21 2007/12/12 22:55:43 lukem Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#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 +#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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +#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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 +#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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 +#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 + +#include +#include +#include +#include +#include +#include + +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 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 \|. +.\" 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 \*(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 +#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 +#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 + +#ifndef lint +__RCSID("$NetBSD: arith_token.c,v 1.7 2017/12/17 04:06:03 kre Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include + +#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 . 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 . 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 + +#ifndef lint +__RCSID("$NetBSD: arithmetic.c,v 1.5 2018/04/21 23:01:29 kre Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include + +#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 +#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 +__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 +#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 +#include +#include +#include +#include +#include + +/* + * 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 +#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 +#include +#include +#include +#include +#include + +#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 + +/* + * 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 + +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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 ' act as if + 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 +#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 +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 : "<>")); + 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 + +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 +#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 +#include +#include +#include +#include +/* + * 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 +#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 /* defines BUFSIZ */ +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef BSD +#include +#include +#include +#endif +#include + +#include "shell.h" +#if JOBS +#if OLD_TTY_DRIVER +#include "sgtty.h" +#else +#include +#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 + +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 : "") : "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 : "")); + 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 +#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 +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include +#include +#include + + +#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 +#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 +#include + +#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 +#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 /* quad_t */ +#include /* BSD4_4 */ +#include +#include +#include +#include +#include +#include +#include + +#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 + +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 + 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 + +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 +#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 +#include +#include +#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 + +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 +#include + +/* + * 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< +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 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 +#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 +#include +#include + +#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 */ + const char letter; /* set [+/-] and $- */ + const char opt_set; /* mutually exclusive option set */ + unsigned char val; /* value of 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 +#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 /* quad_t */ +#include /* BSD4_4 */ +#include + +#include /* defines BUFSIZ */ +#include +#include +#include +#include + +#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 + +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 +#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 +#include +#include + +#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 = ≈ + 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 ""; + } + 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 +#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 +#include /* PIPE_BUF */ +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 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 & && \&( \&) \&; ;; ;& \&| || +.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 , +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 < 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 + +#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 + +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)< +#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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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, "", 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, " ! 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); + 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<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 + +#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 +__RCSID("$NetBSD: syntax.c,v 1.7 2018/12/03 06:40:26 kre Exp $"); + +#include +#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 +#include + +/* 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 +#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 +#include +#include +#include +#include +#include + +#undef CEOF /* from but concflicts with sh use */ + +#include +#include + +#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 +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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 +INCLUDE +INCLUDE +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 +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +__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 +#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 + +#include +#include +#include +#include +#include + +#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("")) + 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 +#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 + +#include +#include +#include + +#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 +#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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#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 + +#include +#include +#include + +#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 +#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 + +#include +#include +#include + +#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 (""); + + 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 +#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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include + +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 \> 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 \< 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 +#ifndef lint +__RCSID("$NetBSD: test.c,v 1.43 2018/09/13 22:00:58 kre Exp $"); +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 ::= +*/ + +enum token { + EOI, + FILRD, + FILWR, + FILEX, + FILEXIST, + FILREG, + FILDIR, + FILCDEV, + FILBDEV, + FILFIFO, + FILSOCK, + FILSYM, + FILGZ, + FILTT, + FILSUID, + FILSGID, + FILSTCK, + FILNT, + FILOT, + FILEQ, + FILUID, + FILGID, + STREZ, + STRNZ, + STREQ, + STRNE, + STRLT, + STRGT, + INTEQ, + INTNE, + INTGE, + INTGT, + INTLE, + INTLT, + UNOT, + BAND, + BOR, + LPAREN, + RPAREN, + OPERAND +}; + +enum token_types { + UNOP, + BINOP +#ifndef SMALL + , + BUNOP, + BBINOP, + PAREN +#endif +}; + +struct t_op { + const char *op_text; + short op_num, op_type; +}; + +static const struct t_op cop[] = { +#ifndef SMALL + {"!", UNOT, BUNOP}, + {"(", LPAREN, PAREN}, + {")", RPAREN, PAREN}, +#endif + {"<", STRLT, BINOP}, + {"=", STREQ, BINOP}, + {">", STRGT, BINOP}, +}; + +static const struct t_op cop2[] = { + {"!=", STRNE, BINOP}, +}; + +static const struct t_op mop3[] = { + {"ef", FILEQ, BINOP}, + {"eq", INTEQ, BINOP}, + {"ge", INTGE, BINOP}, + {"gt", INTGT, BINOP}, + {"le", INTLE, BINOP}, + {"lt", INTLT, BINOP}, + {"ne", INTNE, BINOP}, + {"nt", FILNT, BINOP}, + {"ot", FILOT, BINOP}, +}; + +static const struct t_op mop2[] = { + {"G", FILGID, UNOP}, + {"L", FILSYM, UNOP}, + {"O", FILUID, UNOP}, + {"S", FILSOCK,UNOP}, +#ifndef SMALL + {"a", BAND, BBINOP}, +#endif + {"b", FILBDEV,UNOP}, + {"c", FILCDEV,UNOP}, + {"d", FILDIR, UNOP}, + {"e", FILEXIST,UNOP}, + {"f", FILREG, UNOP}, + {"g", FILSGID,UNOP}, + {"h", FILSYM, UNOP}, /* for backwards compat */ + {"k", FILSTCK,UNOP}, + {"n", STRNZ, UNOP}, +#ifndef SMALL + {"o", BOR, BBINOP}, +#endif + {"p", FILFIFO,UNOP}, + {"r", FILRD, UNOP}, + {"s", FILGZ, UNOP}, + {"t", FILTT, UNOP}, + {"u", FILSUID,UNOP}, + {"w", FILWR, UNOP}, + {"x", FILEX, UNOP}, + {"z", STREZ, UNOP}, +}; + +#ifndef SMALL +static char **t_wp; +static struct t_op const *t_wp_op; +#endif + +#ifndef SMALL +__dead static void syntax(const char *, const char *); +static int oexpr(enum token); +static int aexpr(enum token); +static int nexpr(enum token); +static int primary(enum token); +static int binop(void); +static enum token t_lex(char *); +static int isoperand(void); +#endif +static struct t_op const *findop(const char *); +static int perform_unop(enum token, const char *); +static int perform_binop(enum token, const char *, const char *); +static int test_access(struct stat *, mode_t); +static int filstat(const char *, enum token); +static long long getn(const char *); +static int newerf(const char *, const char *); +static int olderf(const char *, const char *); +static int equalf(const char *, const char *); + +static int one_arg(const char *); +static int two_arg(const char *, const char *); +static int three_arg(const char *, const char *, const char *); +static int four_arg(const char *, const char *, const char *, const char *); + +#if defined(SHELL) +extern void error(const char *, ...) __dead __printflike(1, 2); +extern void *ckmalloc(size_t); +#else +static void error(const char *, ...) __dead __printflike(1, 2); + +static void +error(const char *msg, ...) +{ + va_list ap; + + va_start(ap, msg); + verrx(2, msg, ap); + /*NOTREACHED*/ + va_end(ap); +} + +static void *ckmalloc(size_t); +static void * +ckmalloc(size_t nbytes) +{ + void *p = malloc(nbytes); + + if (!p) + error("Not enough memory!"); + return p; +} +#endif + +#ifdef SHELL +int testcmd(int, char **); + +int +testcmd(int argc, char **argv) +#else +int +main(int argc, char *argv[]) +#endif +{ + int res; + const char *argv0; + +#ifdef SHELL + argv0 = argv[0]; +#else + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + argv0 = getprogname(); +#endif + if (strcmp(argv0, "[") == 0) { + if (strcmp(argv[--argc], "]")) + error("missing ]"); + argv[argc] = NULL; + } + + /* + * POSIX defines operations of test for up to 4 args + * (depending upon what the args are in some cases) + * + * arg count does not include the command name, (but argc does) + * nor the closing ']' when the command was '[' (removed above) + * + * None of the following allow -a or -o as an operator (those + * only apply in the evaluation of unspeicified expressions) + * + * Note that the xxx_arg() functions return "shell" true/false + * (0 == true, 1 == false) or -1 for "unspecified case" + * + * Other functions return C true/false (1 == true, 0 == false) + * + * Hence we simply return the result from xxx_arg(), but + * invert the result of oexpr() below before returning it. + */ + switch (argc - 1) { + case -1: /* impossible, but never mind */ + case 0: /* test $a where a='' false */ + return 1; + + case 1: /* test "$a" */ + return one_arg(argv[1]); /* always works */ + + case 2: /* test op "$a" */ + res = two_arg(argv[1], argv[2]); + if (res >= 0) + return res; + break; + + case 3: /* test "$a" op "$b" or test ! op "$a" */ + res = three_arg(argv[1], argv[2], argv[3]); + if (res >= 0) + return res; + break; + + case 4: /* test ! "$a" op "$b" or test ( op "$a" ) */ + res = four_arg(argv[1], argv[2], argv[3], argv[4]); + if (res >= 0) + return res; + break; + + default: + break; + } + + /* + * All other cases produce unspecified results + * (including cases above with small arg counts where the + * args are not what was expected to be seen) + * + * We fall back to the old method, of attempting to parse + * the expr (highly ambiguous as there is no distinction between + * operators and operands that happen to look like operators) + */ + +#ifdef SMALL + error("SMALL test, no fallback usage"); +#else + + t_wp = &argv[1]; + res = !oexpr(t_lex(*t_wp)); + + if (*t_wp != NULL && *++t_wp != NULL) + syntax(*t_wp, "unexpected operator"); + + return res; +#endif +} + +#ifndef SMALL +static void +syntax(const char *op, const char *msg) +{ + + if (op && *op) + error("%s: %s", op, msg); + else + error("%s", msg); +} +#endif + +static int +one_arg(const char *arg) +{ + /* + * True (exit 0, so false...) if arg is not a null string + * False (so exit 1, so true) if it is. + */ + return *arg == '\0'; +} + +static int +two_arg(const char *a1, const char *a2) +{ + static struct t_op const *op; + + if (a1[0] == '!' && a1[1] == 0) + return !one_arg(a2); + + op = findop(a1); + if (op != NULL && op->op_type == UNOP) + return !perform_unop(op->op_num, a2); + +#ifndef TINY + /* + * an extension, but as we've entered the realm of the unspecified + * we're allowed... test ( $a ) where a='' + */ + if (a1[0] == '(' && a2[0] == ')' && (a1[1] | a2[1]) == 0) + return 1; +#endif + + return -1; +} + +static int +three_arg(const char *a1, const char *a2, const char *a3) +{ + static struct t_op const *op; + int res; + + op = findop(a2); + if (op != NULL && op->op_type == BINOP) + return !perform_binop(op->op_num, a1, a3); + + if (a1[1] != '\0') + return -1; + + if (a1[0] == '!') { + res = two_arg(a2, a3); + if (res >= 0) + res = !res; + return res; + } + +#ifndef TINY + if (a1[0] == '(' && a3[0] == ')' && a3[1] == '\0') + return one_arg(a2); +#endif + + return -1; +} + +static int +four_arg(const char *a1, const char *a2, const char *a3, const char *a4) +{ + int res; + + if (a1[1] != '\0') + return -1; + + if (a1[0] == '!') { + res = three_arg(a2, a3, a4); + if (res >= 0) + res = !res; + return res; + } + +#ifndef TINY + if (a1[0] == '(' && a4[0] == ')' && a4[1] == '\0') + return two_arg(a2, a3); +#endif + + return -1; +} + +#ifndef SMALL +static int +oexpr(enum token n) +{ + int res; + + res = aexpr(n); + if (*t_wp == NULL) + return res; + if (t_lex(*++t_wp) == BOR) + return oexpr(t_lex(*++t_wp)) || res; + t_wp--; + return res; +} + +static int +aexpr(enum token n) +{ + int res; + + res = nexpr(n); + if (*t_wp == NULL) + return res; + if (t_lex(*++t_wp) == BAND) + return aexpr(t_lex(*++t_wp)) && res; + t_wp--; + return res; +} + +static int +nexpr(enum token n) +{ + + if (n == UNOT) + return !nexpr(t_lex(*++t_wp)); + return primary(n); +} + +static int +primary(enum token n) +{ + enum token nn; + int res; + + if (n == EOI) + return 0; /* missing expression */ + if (n == LPAREN) { + if ((nn = t_lex(*++t_wp)) == RPAREN) + return 0; /* missing expression */ + res = oexpr(nn); + if (t_lex(*++t_wp) != RPAREN) + syntax(NULL, "closing paren expected"); + return res; + } + if (t_wp_op && t_wp_op->op_type == UNOP) { + /* unary expression */ + if (*++t_wp == NULL) + syntax(t_wp_op->op_text, "argument expected"); + return perform_unop(n, *t_wp); + } + + if (t_lex(t_wp[1]), t_wp_op && t_wp_op->op_type == BINOP) { + return binop(); + } + + return strlen(*t_wp) > 0; +} +#endif /* !SMALL */ + +static int +perform_unop(enum token n, const char *opnd) +{ + switch (n) { + case STREZ: + return strlen(opnd) == 0; + case STRNZ: + return strlen(opnd) != 0; + case FILTT: + return isatty((int)getn(opnd)); + default: + return filstat(opnd, n); + } +} + +#ifndef SMALL +static int +binop(void) +{ + const char *opnd1, *opnd2; + struct t_op const *op; + + opnd1 = *t_wp; + (void) t_lex(*++t_wp); + op = t_wp_op; + + if ((opnd2 = *++t_wp) == NULL) + syntax(op->op_text, "argument expected"); + + return perform_binop(op->op_num, opnd1, opnd2); +} +#endif + +static int +perform_binop(enum token op_num, const char *opnd1, const char *opnd2) +{ + switch (op_num) { + case STREQ: + return strcmp(opnd1, opnd2) == 0; + case STRNE: + return strcmp(opnd1, opnd2) != 0; + case STRLT: + return strcmp(opnd1, opnd2) < 0; + case STRGT: + return strcmp(opnd1, opnd2) > 0; + case INTEQ: + return getn(opnd1) == getn(opnd2); + case INTNE: + return getn(opnd1) != getn(opnd2); + case INTGE: + return getn(opnd1) >= getn(opnd2); + case INTGT: + return getn(opnd1) > getn(opnd2); + case INTLE: + return getn(opnd1) <= getn(opnd2); + case INTLT: + return getn(opnd1) < getn(opnd2); + case FILNT: + return newerf(opnd1, opnd2); + case FILOT: + return olderf(opnd1, opnd2); + case FILEQ: + return equalf(opnd1, opnd2); + default: + abort(); + /* NOTREACHED */ + } +} + +/* + * The manual, and IEEE POSIX 1003.2, suggests this should check the mode bits, + * not use access(): + * + * True shall indicate only that the write flag is on. The file is not + * writable on a read-only file system even if this test indicates true. + * + * Unfortunately IEEE POSIX 1003.1-2001, as quoted in SuSv3, says only: + * + * True shall indicate that permission to read from file will be granted, + * as defined in "File Read, Write, and Creation". + * + * and that section says: + * + * When a file is to be read or written, the file shall be opened with an + * access mode corresponding to the operation to be performed. If file + * access permissions deny access, the requested operation shall fail. + * + * and of course access permissions are described as one might expect: + * + * * If a process has the appropriate privilege: + * + * * If read, write, or directory search permission is requested, + * access shall be granted. + * + * * If execute permission is requested, access shall be granted if + * execute permission is granted to at least one user by the file + * permission bits or by an alternate access control mechanism; + * otherwise, access shall be denied. + * + * * Otherwise: + * + * * The file permission bits of a file contain read, write, and + * execute/search permissions for the file owner class, file group + * class, and file other class. + * + * * Access shall be granted if an alternate access control mechanism + * is not enabled and the requested access permission bit is set for + * the class (file owner class, file group class, or file other class) + * to which the process belongs, or if an alternate access control + * mechanism is enabled and it allows the requested access; otherwise, + * access shall be denied. + * + * and when I first read this I thought: surely we can't go about using + * open(O_WRONLY) to try this test! However the POSIX 1003.1-2001 Rationale + * section for test does in fact say: + * + * On historical BSD systems, test -w directory always returned false + * because test tried to open the directory for writing, which always + * fails. + * + * and indeed this is in fact true for Seventh Edition UNIX, UNIX 32V, and UNIX + * System III, and thus presumably also for BSD up to and including 4.3. + * + * Secondly I remembered why using open() and/or access() are bogus. They + * don't work right for detecting read and write permissions bits when called + * by root. + * + * Interestingly the 'test' in 4.4BSD was closer to correct (as per + * 1003.2-1992) and it was implemented efficiently with stat() instead of + * open(). + * + * This was apparently broken in NetBSD around about 1994/06/30 when the old + * 4.4BSD implementation was replaced with a (arguably much better coded) + * implementation derived from pdksh. + * + * Note that modern pdksh is yet different again, but still not correct, at + * least not w.r.t. 1003.2-1992. + * + * As I think more about it and read more of the related IEEE docs I don't like + * that wording about 'test -r' and 'test -w' in 1003.1-2001 at all. I very + * much prefer the original wording in 1003.2-1992. It is much more useful, + * and so that's what I've implemented. + * + * (Note that a strictly conforming implementation of 1003.1-2001 is in fact + * totally useless for the case in question since its 'test -w' and 'test -r' + * can never fail for root for any existing files, i.e. files for which 'test + * -e' succeeds.) + * + * The rationale for 1003.1-2001 suggests that the wording was "clarified" in + * 1003.1-2001 to align with the 1003.2b draft. 1003.2b Draft 12 (July 1999), + * which is the latest copy I have, does carry the same suggested wording as is + * in 1003.1-2001, with its rationale saying: + * + * This change is a clarification and is the result of interpretation + * request PASC 1003.2-92 #23 submitted for IEEE Std 1003.2-1992. + * + * That interpretation can be found here: + * + * http://www.pasc.org/interps/unofficial/db/p1003.2/pasc-1003.2-23.html + * + * Not terribly helpful, unfortunately. I wonder who that fence sitter was. + * + * Worse, IMVNSHO, I think the authors of 1003.2b-D12 have mis-interpreted the + * PASC interpretation and appear to be gone against at least one widely used + * implementation (namely 4.4BSD). The problem is that for file access by root + * this means that if test '-r' and '-w' are to behave as if open() were called + * then there's no way for a shell script running as root to check if a file + * has certain access bits set other than by the grotty means of interpreting + * the output of 'ls -l'. This was widely considered to be a bug in V7's + * "test" and is, I believe, one of the reasons why direct use of access() was + * avoided in some more recent implementations! + * + * I have always interpreted '-r' to match '-w' and '-x' as per the original + * wording in 1003.2-1992, not the other way around. I think 1003.2b goes much + * too far the wrong way without any valid rationale and that it's best if we + * stick with 1003.2-1992 and test the flags, and not mimic the behaviour of + * open() since we already know very well how it will work -- existance of the + * file is all that matters to open() for root. + * + * Unfortunately the SVID is no help at all (which is, I guess, partly why + * we're in this mess in the first place :-). + * + * The SysV implementation (at least in the 'test' builtin in /bin/sh) does use + * access(name, 2) even though it also goes to much greater lengths for '-x' + * matching the 1003.2-1992 definition (which is no doubt where that definition + * came from). + * + * The ksh93 implementation uses access() for '-r' and '-w' if + * (euid==uid&&egid==gid), but uses st_mode for '-x' iff running as root. + * i.e. it does strictly conform to 1003.1-2001 (and presumably 1003.2b). + */ +static int +test_access(struct stat *sp, mode_t stmode) +{ + gid_t *groups; + register int n; + uid_t euid; + int maxgroups; + + /* + * I suppose we could use access() if not running as root and if we are + * running with ((euid == uid) && (egid == gid)), but we've already + * done the stat() so we might as well just test the permissions + * directly instead of asking the kernel to do it.... + */ + euid = geteuid(); + if (euid == 0) /* any bit is good enough */ + stmode = (stmode << 6) | (stmode << 3) | stmode; + else if (sp->st_uid == euid) + stmode <<= 6; + else if (sp->st_gid == getegid()) + stmode <<= 3; + else { + /* XXX stolen almost verbatim from ksh93.... */ + /* on some systems you can be in several groups */ + if ((maxgroups = getgroups(0, NULL)) <= 0) + maxgroups = NGROUPS_MAX; /* pre-POSIX system? */ + groups = ckmalloc((maxgroups + 1) * sizeof(gid_t)); + n = getgroups(maxgroups, groups); + while (--n >= 0) { + if (groups[n] == sp->st_gid) { + stmode <<= 3; + break; + } + } + free(groups); + } + + return sp->st_mode & stmode; +} + +static int +filstat(const char *nm, enum token mode) +{ + struct stat s; + + if (mode == FILSYM ? lstat(nm, &s) : stat(nm, &s)) + return 0; + + switch (mode) { + case FILRD: + return test_access(&s, S_IROTH); + case FILWR: + return test_access(&s, S_IWOTH); + case FILEX: + return test_access(&s, S_IXOTH); + case FILEXIST: + return 1; /* the successful lstat()/stat() is good enough */ + case FILREG: + return S_ISREG(s.st_mode); + case FILDIR: + return S_ISDIR(s.st_mode); + case FILCDEV: + return S_ISCHR(s.st_mode); + case FILBDEV: + return S_ISBLK(s.st_mode); + case FILFIFO: + return S_ISFIFO(s.st_mode); + case FILSOCK: + return S_ISSOCK(s.st_mode); + case FILSYM: + return S_ISLNK(s.st_mode); + case FILSUID: + return (s.st_mode & S_ISUID) != 0; + case FILSGID: + return (s.st_mode & S_ISGID) != 0; + case FILSTCK: + return (s.st_mode & S_ISVTX) != 0; + case FILGZ: + return s.st_size > (off_t)0; + case FILUID: + return s.st_uid == geteuid(); + case FILGID: + return s.st_gid == getegid(); + default: + return 1; + } +} + +#define VTOC(x) (const unsigned char *)((const struct t_op *)x)->op_text + +static int +compare1(const void *va, const void *vb) +{ + const unsigned char *a = va; + const unsigned char *b = VTOC(vb); + + return a[0] - b[0]; +} + +static int +compare2(const void *va, const void *vb) +{ + const unsigned char *a = va; + const unsigned char *b = VTOC(vb); + int z = a[0] - b[0]; + + return z ? z : (a[1] - b[1]); +} + +static struct t_op const * +findop(const char *s) +{ + if (s[0] == '-') { + if (s[1] == '\0') + return NULL; + if (s[2] == '\0') + return bsearch(s + 1, mop2, __arraycount(mop2), + sizeof(*mop2), compare1); + else if (s[3] != '\0') + return NULL; + else + return bsearch(s + 1, mop3, __arraycount(mop3), + sizeof(*mop3), compare2); + } else { + if (s[1] == '\0') + return bsearch(s, cop, __arraycount(cop), sizeof(*cop), + compare1); + else if (strcmp(s, cop2[0].op_text) == 0) + return cop2; + else + return NULL; + } +} + +#ifndef SMALL +static enum token +t_lex(char *s) +{ + struct t_op const *op; + + if (s == NULL) { + t_wp_op = NULL; + return EOI; + } + + if ((op = findop(s)) != NULL) { + if (!((op->op_type == UNOP && isoperand()) || + (op->op_num == LPAREN && *(t_wp+1) == 0))) { + t_wp_op = op; + return op->op_num; + } + } + t_wp_op = NULL; + return OPERAND; +} + +static int +isoperand(void) +{ + struct t_op const *op; + char *s, *t; + + if ((s = *(t_wp+1)) == 0) + return 1; + if ((t = *(t_wp+2)) == 0) + return 0; + if ((op = findop(s)) != NULL) + return op->op_type == BINOP && (t[0] != ')' || t[1] != '\0'); + return 0; +} +#endif + +/* atoi with error detection */ +static long long +getn(const char *s) +{ + char *p; + long long r; + + errno = 0; + r = strtoll(s, &p, 10); + + if (errno != 0) + if (errno == ERANGE && (r == LLONG_MAX || r == LLONG_MIN)) + error("%s: out of range", s); + + if (p != s) + while (isspace((unsigned char)*p)) + p++; + + if (*p || p == s) + error("'%s': bad number", s); + + return r; +} + +static int +newerf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, >)); +} + +static int +olderf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + timespeccmp(&b1.st_mtim, &b2.st_mtim, <)); +} + +static int +equalf(const char *f1, const char *f2) +{ + struct stat b1, b2; + + return (stat(f1, &b1) == 0 && + stat(f2, &b2) == 0 && + b1.st_dev == b2.st_dev && + b1.st_ino == b2.st_ino); +} diff --git a/usr.bin/asa/asa.1 b/usr.bin/asa/asa.1 new file mode 100644 index 0000000..ab76d28 --- /dev/null +++ b/usr.bin/asa/asa.1 @@ -0,0 +1,85 @@ +.\" $NetBSD: asa.1,v 1.14 2017/07/03 21:34:18 wiz Exp $ +.\" +.\" Copyright (c) 1993 Winning Strategies, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by Winning Strategies, Inc. +.\" 4. The name of the author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 2, 2016 +.Dt ASA 1 +.Os +.Sh NAME +.Nm asa +.Nd interpret carriage-control characters +.Sh SYNOPSIS +.Nm +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility reads files sequentially, mapping +.Tn FORTRAN +carriage-control characters to line-printer control sequences, +and writes them to the standard output. +.Pp +The first character of each line is interpreted as a carriage-control +character. The following characters are interpreted as follows: +.Bl -tag -width "" +.It +Output the rest of the line without change. +.It 0 +Output a character before printing the rest of the line. +.It 1 +Output a character before printing the rest of the line. +.It + +The trailing of the previous line is replaced by a +before printing the rest of the line. +.El +.Pp +Lines beginning with characters other than the above are treated as if they +begin with . +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +To view a file containing the output of a +.Tn FORTRAN +program: +.Dl asa file +.Pp +To format the output of a +.Tn FORTRAN +program and redirect it to a line-printer: +.Dl a.out | asa | lpr +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 +and to +.St -xcu5 . +.Sh AUTHORS +.An J.T. Conklin , +Winning Strategies, Inc. diff --git a/usr.bin/asa/asa.c b/usr.bin/asa/asa.c new file mode 100644 index 0000000..6c1efd6 --- /dev/null +++ b/usr.bin/asa/asa.c @@ -0,0 +1,116 @@ +/* $NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $ */ + +/* + * Copyright (c) 1993,94 Winning Strategies, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Winning Strategies, Inc. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: asa.c,v 1.17 2016/09/05 00:40:28 sevan Exp $"); +#endif + +#include +#include +#include + +static void asa(FILE *); + +int +/*ARGSUSED*/ +main (int argc, char *argv[]) +{ + FILE *fp; + + /* skip progname */ + argv++; + + if (*argv == NULL) + asa(stdin); + else + do { + if ((fp = fopen(*argv, "r")) == NULL) { + warn("%s", *argv); + continue; + } + asa(fp); + (void)fclose(fp); + } while (*++argv != NULL); + + return 0; +} + +static void +asa(FILE *f) +{ + char *buf; + size_t len; + + if ((buf = fgetln(f, &len)) != NULL) { + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + /* special case the first line */ + switch (buf[0]) { + case '0': + (void)putchar('\n'); + break; + case '1': + (void)putchar('\f'); + break; + } + + if (len > 1 && buf[0] && buf[1]) + (void)fwrite(buf + 1, 1, len - 1, stdout); + + while ((buf = fgetln(f, &len)) != NULL) { + if (len > 0 && buf[len - 1] == '\n') + buf[--len] = '\0'; + switch (buf[0]) { + default: + case ' ': + (void)putchar('\n'); + break; + case '0': + (void)putchar('\n'); + (void)putchar('\n'); + break; + case '1': + (void)putchar('\f'); + break; + case '+': + (void)putchar('\r'); + break; + } + + if (len > 1 && buf[0] && buf[1]) + (void)fwrite(buf + 1, 1, len - 1, stdout); + } + + (void)putchar('\n'); + } +} diff --git a/usr.bin/at/at.1 b/usr.bin/at/at.1 new file mode 100644 index 0000000..cdd97e1 --- /dev/null +++ b/usr.bin/at/at.1 @@ -0,0 +1,339 @@ +.\" $NetBSD: at.1,v 1.29 2016/11/18 12:16:48 abhinav Exp $ +.\" $OpenBSD: at.1,v 1.6 1998/06/05 00:47:46 deraadt Exp $ +.\" $FreeBSD: at.man,v 1.6 1997/02/22 19:54:05 peter Exp $ +.Dd March 10, 2008 +.Dt AT 1 +.Os +.Sh NAME +.Nm at , +.Nm batch , +.Nm atq , +.Nm atrm +.Nd queue, examine or delete jobs for later execution +.Sh SYNOPSIS +.Nm at +.Op Fl bdlmrVv +.Op Fl f Ar file +.Op Fl q Ar queue +.Fl t Ar [[CC]YY]MMDDhhmm[.SS] +.Nm +.Op Fl bdlmrVv +.Op Fl f Ar file +.Op Fl q Ar queue +.Ar time +.Nm +.Op Fl V +.Fl c Ar job Op Ar job ... +.Nm atq +.Op Fl Vv +.Op Fl q Ar queue +.Nm atrm +.Op Fl V +.Ar job +.Op Ar job ... +.Nm batch +.Op Fl mVv +.Op Fl f Ar file +.Op Fl q Ar queue +.Op Fl t Ar [[CC]YY]MMDDhhmm[.SS] +.Nm batch +.Op Fl mVv +.Op Fl f Ar file +.Op Fl q Ar queue +.Op Ar time +.Sh DESCRIPTION +.Nm +and +.Nm batch +read commands from standard input or a specified file which +are to be executed at a later time, using +.Xr sh 1 . +.Bl -tag -width indent +.It Nm at +Executes commands at a specified time. +.It Nm atq +Lists the user's pending jobs, unless the user is the superuser. +In that case, everybody's jobs are listed. +.It Nm atrm +Deletes jobs. +.It Nm batch +Executes commands when system load levels permit. +In other words, when +the load average drops below 1.5, or the value specified in the invocation of +.Xr atrun 8 . +.El +.Pp +.Nm +allows some moderately complex +.Ar time +specifications. +It accepts times of the form +.Ar HHMM +or +.Ar HH:MM +to run a job at a specific time of day. +(If that time is already past, the next day is assumed.) +You may also specify +.Sq midnight , +.Sq noon , +or +.Sq teatime +(4pm) +and you can have a time-of-day suffixed with +.Sq AM +or +.Sq PM +for running in the morning or the evening. +You can also say what day the job will be run, +by giving a date in the form +.Ar %month-name day +with an optional +.Ar year , +or giving a date of the form +.Ar MMDDYY +or +.Ar MM/DD/YY +or +.Ar DD.MM.YY . +The specification of a date must follow the specification of +the time of day. +You can also give times like +.Op Nm now +or +.Op Nm now +.Sq + Ar count %time-units , +where the time-units can be +.Sq minutes , +.Sq hours , +.Sq days , +.Sq weeks , +.Sq months , +or +.Sq years +and you can tell +.Nm +to run the job today by suffixing the time with +.Sq today +and to run the job tomorrow by suffixing the time with +.Sq tomorrow . +.Pp +For example, to run a job at 4pm three days from now, you would do +.Dl at 4pm + 3 days , +to run a job at 10:00am on July 31, you would do +.Dl at 10am Jul 31 +and to run a job at 1am tomorrow, you would do +.Dl at 1am tomorrow . +.Pp +Alternatively the time may be specified in a language-neutral fashion +by using the +.Fl t +options. +.Pp +For both +.Nm +and +.Nm batch , +commands are read from standard input or the file specified +with the +.Fl f +option and executed. +The working directory, the environment (except for the variables +.Ev TERM , +.Ev TERMCAP , +.Ev DISPLAY +and +.Ev _ ) +and the +.Ar umask +are retained from the time of invocation. +An +.Nm +or +.Nm batch +command invoked from a +.Xr su 1 +shell will retain the current userid. +The user will be mailed standard error and standard output from his +commands, if any. +Mail will be sent using the command +.Xr sendmail 1 . +If +.Nm +is executed from a +.Xr su 1 +shell, the owner of the login shell will receive the mail. +.Pp +The superuser may use these commands in any case. +For other users, permission to use +.Nm at +is determined by the files +.Pa /var/at/at.allow +and +.Pa /var/at/at.deny . +.Pp +If the file +.Pa /var/at/at.allow +exists, only usernames mentioned in it are allowed to use +.Nm . +.Pp +If +.Pa /var/at/at.allow +does not exist, +.Pa /var/at/at.deny +is checked; every username not mentioned in it is then allowed +to use +.Nm . +.Pp +If neither exists, only the superuser is allowed use of +.Nm . +.Pp +An empty +.Pa /var/at/at.deny +means that every user is allowed use these commands. +This is the default configuration. +.Sh OPTIONS +.Bl -tag -offset indent -width XqXqueueXX +.It Fl b +Is an alias for +.Nm batch . +.It Fl c +Cats the jobs listed on the command line to standard output. +.It Fl d +Is an alias for +.Nm atrm . +.It Fl f Ar file +Reads the job from +.Ar file +rather than standard input. +.It Fl l +Is an alias for +.Nm atq . +.It Fl m +Send mail to the user when the job has completed even if there was no +output. +.It Fl q Ar queue +Uses the specified queue. +A queue designation consists of a single letter. +Valid queue designations +range from +.Sq a +to +.Sq z +and +.Sq A +to +.Sq Z . +The +.Sq c +queue is the default for +.Nm +and the +.Sq E +queue for +.Nm batch . +Queues with higher letters run with increased niceness. +If a job is submitted to a queue designated with an uppercase letter, it +is treated as if it had been submitted to batch at that time. +If +.Nm atq +is given a specific queue, it will only show jobs pending in that queue. +.It Fl r +Is an alias for +.Nm atrm . +.It Fl t +For both +.Nm +and +.Nm batch , +the time may be specified in a language-neutral format consisting of: +.Bl -tag -width Ds -compact -offset indent +.It Ar CC +The first two digits of the year (the century). +.It Ar YY +The second two digits of the year. +If +.Ar YY +is specified, but +.Ar CC +is not, a value for +.Ar YY +between 69 and 99 results in a +.Ar CC +value of 19. +Otherwise, a +.Ar CC +value of 20 is used. +.It Ar MM +The month of the year, from 01 to 12. +.It Ar \&DD +The day of the month, from 01 to 31. +.It Ar hh +The hour of the day, from 00 to 23. +.It Ar mm +The minute of the hour, from 00 to 59. +.It Ar \&SS +The second of the minute, from 00 to 61. +.El +.It Fl V +Prints the version number to standard error. +.It Fl v +For +.Nm atq , +shows completed but not yet deleted jobs in the queue. +Otherwise shows the time the job will be executed. +.El +.Sh FILES +.Bl -tag -width /var/at/.lockfile -compact +.It Pa /var/at/jobs +Directory containing job files +.It Pa /var/at/spool +Directory containing output spool files +.It Pa /var/run/utmp +Login records +.It Pa /var/at/at.allow +Allow permission control +.It Pa /var/at/at.deny +Deny permission control +.It Pa /var/at/.lockfile +Job-creation lock file. +.El +.Sh SEE ALSO +.Xr nice 1 , +.Xr sendmail 1 , +.Xr sh 1 , +.Xr umask 2 , +.Xr atrun 8 , +.Xr cron 8 +.Sh STANDARDS +The +.Nm +and +.Nm batch +utilities conform to +.St -p1003.2-92 . +.Sh AUTHORS +.An -nosplit +.Nm +was mostly written by +.An Thomas Koenig Aq Mt ig25@rz.uni-karlsruhe.de . +The time parsing routines are implemented by +.An David Parsons Aq Mt orc@pell.chi.il.us . +.Sh BUGS +If the file +.Pa /var/run/utmp +is not available or corrupted, or if the user is not logged on at the +time +.Nm +is invoked, the mail is sent to the userid found +in the environment variable +.Ev LOGNAME . +If that is undefined or empty, the current userid is assumed. +.Pp +.Nm +and +.Nm batch +as presently implemented are not suitable when users are competing for +resources. +If this is the case for your site, you might want to consider another +batch system, such as +.Ic nqs . diff --git a/usr.bin/at/at.c b/usr.bin/at/at.c new file mode 100644 index 0000000..621b95d --- /dev/null +++ b/usr.bin/at/at.c @@ -0,0 +1,758 @@ +/* $NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $ */ + +/* + * at.c : Put file into atrun queue + * Copyright (C) 1993, 1994 Thomas Koenig + * + * Atrun & Atq modifications + * Copyright (C) 1993 David Parsons + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* System Headers */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local headers */ +#include "at.h" +#include "panic.h" +#include "parsetime.h" +#include "perm.h" +#include "pathnames.h" +#include "stime.h" +#include "privs.h" + +/* Macros */ +#define ALARMC 10 /* Number of seconds to wait for timeout */ + +#define TIMESIZE 50 + +enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */ + +/* File scope variables */ +#ifndef lint +#if 0 +static char rcsid[] = "$OpenBSD: at.c,v 1.15 1998/06/03 16:20:26 deraadt Exp $"; +#else +__RCSID("$NetBSD: at.c,v 1.31 2016/03/13 00:32:09 dholland Exp $"); +#endif +#endif + +const char *no_export[] = {"TERM", "TERMCAP", "DISPLAY", "_"}; +static int send_mail = 0; + +/* External variables */ + +extern char **environ; +bool fcreated = false; +char atfile[FILENAME_MAX]; + +char *atinput = NULL; /* where to get input from */ +unsigned char atqueue = 0; /* which queue to examine for jobs (atq) */ +char atverify = 0; /* verify time instead of queuing job */ + +/* Function declarations */ + +__dead static void sigc (int); +__dead static void alarmc (int); +static char *cwdname (void); +static int nextjob (void); +static void writefile (time_t, unsigned char); +static void list_jobs (void); +static void process_jobs (int, char **, int); + +/* Signal catching functions */ + +/*ARGSUSED*/ +static void +sigc(int signo) +{ + + /* If a signal interrupts us, remove the spool file and exit. */ + if (fcreated) { + privs_enter(); + (void)unlink(atfile); + privs_exit(); + } + (void)raise_default_signal(signo); + exit(EXIT_FAILURE); +} + +/*ARGSUSED*/ +static void +alarmc(int signo) +{ + + /* Time out after some seconds. */ + warnx("File locking timed out"); + sigc(signo); +} + +/* Local functions */ + +static char * +cwdname(void) +{ + + /* + * Read in the current directory; the name will be overwritten on + * subsequent calls. + */ + static char path[MAXPATHLEN]; + + return getcwd(path, sizeof(path)); +} + +static int +nextjob(void) +{ + int jobno; + FILE *fid; + + if ((fid = fopen(_PATH_SEQFILE, "r+")) != NULL) { + if (fscanf(fid, "%5x", &jobno) == 1) { + (void)rewind(fid); + jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */ + (void)fprintf(fid, "%05x\n", jobno); + } else + jobno = EOF; + (void)fclose(fid); + return jobno; + } else if ((fid = fopen(_PATH_SEQFILE, "w")) != NULL) { + (void)fprintf(fid, "%05x\n", jobno = 1); + (void)fclose(fid); + return 1; + } + return EOF; +} + +static void +writefile(time_t runtimer, unsigned char queue) +{ + /* + * This does most of the work if at or batch are invoked for + * writing a job. + */ + int jobno; + char *ap, *ppos; + const char *mailname; + struct passwd *pass_entry; + struct stat statbuf; + int fdes, lockdes, fd2; + FILE *fp, *fpin; + struct sigaction act; + char **atenv; + int ch; + mode_t cmask; + struct flock lock; + + (void)setlocale(LC_TIME, ""); + + /* + * Install the signal handler for SIGINT; terminate after removing the + * spool file if necessary + */ + (void)memset(&act, 0, sizeof(act)); + act.sa_handler = sigc; + (void)sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + (void)sigaction(SIGINT, &act, NULL); + + (void)strlcpy(atfile, _PATH_ATJOBS, sizeof(atfile)); + ppos = atfile + strlen(atfile); + + /* + * Loop over all possible file names for running something at this + * particular time, see if a file is there; the first empty slot at + * any particular time is used. Lock the file _PATH_LOCKFILE first + * to make sure we're alone when doing this. + */ + + privs_enter(); + + if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0) + perr("Cannot open lockfile " _PATH_LOCKFILE); + + lock.l_type = F_WRLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + + act.sa_handler = alarmc; + (void)sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + /* + * Set an alarm so a timeout occurs after ALARMC seconds, in case + * something is seriously broken. + */ + (void)sigaction(SIGALRM, &act, NULL); + (void)alarm(ALARMC); + (void)fcntl(lockdes, F_SETLKW, &lock); + (void)alarm(0); + + if ((jobno = nextjob()) == EOF) + perr("Cannot generate job number"); + + (void)snprintf(ppos, sizeof(atfile) - (ppos - atfile), + "%c%5x%8lx", queue, jobno, (unsigned long) (runtimer/60)); + + for (ap = ppos; *ap != '\0'; ap++) + if (*ap == ' ') + *ap = '0'; + + if (stat(atfile, &statbuf) == -1) + if (errno != ENOENT) + perr("Cannot access " _PATH_ATJOBS); + + /* + * Create the file. The x bit is only going to be set after it has + * been completely written out, to make sure it is not executed in + * the meantime. To make sure they do not get deleted, turn off + * their r bit. Yes, this is a kluge. + */ + cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); + if ((fdes = open(atfile, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR)) == -1) + perr("Cannot create atjob file"); + + if ((fd2 = dup(fdes)) == -1) + perr("Error in dup() of job file"); + + if (fchown(fd2, real_uid, real_gid) == -1) + perr("Cannot give away file"); + + privs_exit(); + + /* + * We've successfully created the file; let's set the flag so it + * gets removed in case of an interrupt or error. + */ + fcreated = true; + + /* Now we can release the lock, so other people can access it */ + lock.l_type = F_UNLCK; + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + (void)fcntl(lockdes, F_SETLKW, &lock); + (void)close(lockdes); + + if ((fp = fdopen(fdes, "w")) == NULL) + panic("Cannot reopen atjob file"); + + /* + * Get the userid to mail to, first by trying getlogin(), which reads + * /etc/utmp, then from $LOGNAME or $USER, finally from getpwuid(). + */ + mailname = getlogin(); + if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL) + mailname = getenv("USER"); + + if (mailname == NULL || mailname[0] == '\0' || + strlen(mailname) > LOGIN_NAME_MAX || getpwnam(mailname) == NULL) { + pass_entry = getpwuid(real_uid); + if (pass_entry != NULL) + mailname = pass_entry->pw_name; + } + + if (atinput != NULL) { + fpin = freopen(atinput, "r", stdin); + if (fpin == NULL) + perr("Cannot open input file"); + } + (void)fprintf(fp, + "#!/bin/sh\n" + "# atrun uid=%u gid=%u\n" + "# mail %s %d\n", + real_uid, real_gid, mailname, send_mail); + + /* Write out the umask at the time of invocation */ + (void)fprintf(fp, "umask %o\n", cmask); + + /* + * Write out the environment. Anything that may look like a special + * character to the shell is quoted, except for \n, which is done + * with a pair of "'s. Dont't export the no_export list (such as + * TERM or DISPLAY) because we don't want these. + */ + for (atenv = environ; *atenv != NULL; atenv++) { + int export = 1; + char *eqp; + + eqp = strchr(*atenv, '='); + if (eqp == NULL) + eqp = *atenv; + else { + size_t i; + + for (i = 0; i < __arraycount(no_export); i++) { + export = export && + strncmp(*atenv, no_export[i], + (size_t)(eqp - *atenv)) != 0; + } + eqp++; + } + + if (export) { + (void)fwrite(*atenv, sizeof(char), + (size_t)(eqp - *atenv), fp); + for (ap = eqp; *ap != '\0'; ap++) { + if (*ap == '\n') + (void)fprintf(fp, "\"\n\""); + else { + if (!isalnum((unsigned char)*ap)) { + switch (*ap) { + case '%': case '/': case '{': + case '[': case ']': case '=': + case '}': case '@': case '+': + case '#': case ',': case '.': + case ':': case '-': case '_': + break; + default: + (void)fputc('\\', fp); + break; + } + } + (void)fputc(*ap, fp); + } + } + (void)fputs("; export ", fp); + (void)fwrite(*atenv, sizeof(char), + (size_t)(eqp - *atenv - 1), fp); + (void)fputc('\n', fp); + } + } + /* + * Cd to the directory at the time and write out all the + * commands the user supplies from stdin. + */ + (void)fputs("cd ", fp); + for (ap = cwdname(); *ap != '\0'; ap++) { + if (*ap == '\n') + (void)fprintf(fp, "\"\n\""); + else { + if (*ap != '/' && !isalnum((unsigned char)*ap)) + (void)fputc('\\', fp); + + (void)fputc(*ap, fp); + } + } + /* + * Test cd's exit status: die if the original directory has been + * removed, become unreadable or whatever. + */ + (void)fprintf(fp, + " || {\n" + "\t echo 'Execution directory inaccessible' >&2\n" + "\t exit 1\n" + "}\n"); + + if ((ch = getchar()) == EOF) + panic("Input error"); + + do { + (void)fputc(ch, fp); + } while ((ch = getchar()) != EOF); + + (void)fprintf(fp, "\n"); + if (ferror(fp)) + panic("Output error"); + + if (ferror(stdin)) + panic("Input error"); + + (void)fclose(fp); + + privs_enter(); + + /* + * Set the x bit so that we're ready to start executing + */ + if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) == -1) + perr("Cannot give away file"); + + privs_exit(); + + (void)close(fd2); + (void)fprintf(stderr, + "Job %d will be executed using /bin/sh\n", jobno); +} + +static void +list_jobs(void) +{ + /* + * List all a user's jobs in the queue, by looping through + * _PATH_ATJOBS, or everybody's if we are root + */ + struct passwd *pw; + DIR *spool; + struct dirent *dirent; + struct stat buf; + struct tm runtime; + unsigned long ctm; + unsigned char queue; + int jobno; + time_t runtimer; + char timestr[TIMESIZE]; + int first = 1; + + privs_enter(); + + if (chdir(_PATH_ATJOBS) == -1) + perr("Cannot change to " _PATH_ATJOBS); + + if ((spool = opendir(".")) == NULL) + perr("Cannot open " _PATH_ATJOBS); + + /* Loop over every file in the directory */ + while ((dirent = readdir(spool)) != NULL) { + if (stat(dirent->d_name, &buf) == -1) + perr("Cannot stat in " _PATH_ATJOBS); + + /* + * See it's a regular file and has its x bit turned on and + * is the user's + */ + if (!S_ISREG(buf.st_mode) + || (buf.st_uid != real_uid && real_uid != 0) + || !(S_IXUSR & buf.st_mode || atverify)) + continue; + + if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) != 3) + continue; + + if (atqueue && queue != atqueue) + continue; + + runtimer = 60 * (time_t)ctm; + runtime = *localtime(&runtimer); +#if 1 + /* + * Provide a consistent date/time format instead of a + * locale-specific one that might have 2 digit years + */ + (void)strftime(timestr, TIMESIZE, "%T %F", &runtime); +#else + (void)strftime(timestr, TIMESIZE, "%X %x", &runtime); +#endif + if (first) { + (void)printf("%-*s %-*s %-*s %s\n", + (int)strlen(timestr), "Date", + LOGIN_NAME_MAX, "Owner", + 7, "Queue", + "Job"); + first = 0; + } + pw = getpwuid(buf.st_uid); + + (void)printf("%s %-*s %c%-*s %d\n", + timestr, + LOGIN_NAME_MAX, pw ? pw->pw_name : "???", + queue, + 6, (S_IXUSR & buf.st_mode) ? "" : "(done)", + jobno); + } + (void)closedir(spool); + privs_exit(); +} + +static void +process_jobs(int argc, char **argv, int what) +{ + /* Delete every argument (job - ID) given */ + int i; + struct stat buf; + DIR *spool; + struct dirent *dirent; + unsigned long ctm; + unsigned char queue; + int jobno; + + privs_enter(); + + if (chdir(_PATH_ATJOBS) == -1) + perr("Cannot change to " _PATH_ATJOBS); + + if ((spool = opendir(".")) == NULL) + perr("Cannot open " _PATH_ATJOBS); + + privs_exit(); + + /* Loop over every file in the directory */ + while((dirent = readdir(spool)) != NULL) { + + privs_enter(); + if (stat(dirent->d_name, &buf) == -1) + perr("Cannot stat in " _PATH_ATJOBS); + privs_exit(); + + if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) !=3) + continue; + + for (i = optind; i < argc; i++) { + if (atoi(argv[i]) == jobno) { + if (buf.st_uid != real_uid && real_uid != 0) + errx(EXIT_FAILURE, + "%s: Not owner", argv[i]); + + switch (what) { + case ATRM: + privs_enter(); + + if (unlink(dirent->d_name) == -1) + perr(dirent->d_name); + + privs_exit(); + break; + + case CAT: { + FILE *fp; + int ch; + + privs_enter(); + + fp = fopen(dirent->d_name, "r"); + + privs_exit(); + + if (!fp) + perr("Cannot open file"); + else { + while((ch = getc(fp)) != EOF) + (void)putchar(ch); + (void)fclose(fp); + } + } + break; + + default: + errx(EXIT_FAILURE, + "Internal error, process_jobs = %d", + what); + break; + } + } + } + } + (void)closedir(spool); +} + +/* Global functions */ + +int +main(int argc, char **argv) +{ + int c; + unsigned char queue = DEFAULT_AT_QUEUE; + char queue_set = 0; + char time_set = 0; + char *pgm; + + int program = AT; /* our default program */ + const char *options = "q:f:t:mvldbrVc"; /* default options for at */ + int disp_version = 0; + time_t timer; + + privs_relinquish(); + + /* Eat any leading paths */ + if ((pgm = strrchr(argv[0], '/')) == NULL) + pgm = argv[0]; + else + pgm++; + + /* find out what this program is supposed to do */ + if (strcmp(pgm, "atq") == 0) { + program = ATQ; + options = "q:vV"; + } else if (strcmp(pgm, "atrm") == 0) { + program = ATRM; + options = "V"; + } else if (strcmp(pgm, "batch") == 0) { + program = BATCH; + options = "f:q:t:mvV"; + } + + /* process whatever options we can process */ + opterr = 1; + while ((c = getopt(argc, argv, options)) != -1) { + switch (c) { + case 'v': /* verify time settings */ + atverify = 1; + break; + + case 'm': /* send mail when job is complete */ + send_mail = 1; + break; + + case 'f': + atinput = optarg; + break; + + case 'q': /* specify queue */ + if (strlen(optarg) > 1) + usage(); + + atqueue = queue = *optarg; + if (!(islower(queue) || isupper(queue))) + usage(); + + queue_set = 1; + break; + case 't': /* touch(1) date format */ + timer = stime(optarg); + time_set = 1; + break; + + case 'd': + case 'r': + if (program != AT) + usage(); + + program = ATRM; + options = "V"; + break; + + case 'l': + if (program != AT) + usage(); + + program = ATQ; + options = "q:vV"; + break; + + case 'b': + if (program != AT) + usage(); + + program = BATCH; + options = "f:q:mvV"; + break; + + case 'V': + disp_version = 1; + break; + + case 'c': + program = CAT; + options = ""; + break; + + default: + usage(); + break; + } + } /* end of options eating */ + + if (disp_version) + (void)fprintf(stderr, "%s version %.1f\n", pgm, AT_VERSION); + + if (!check_permission()) + errx(EXIT_FAILURE, + "You do not have permission to use %s.", pgm); + + /* select our program */ + switch (program) { + case ATQ: + if (optind != argc) + usage(); + list_jobs(); + break; + + case ATRM: + case CAT: + if (optind == argc) + usage(); + process_jobs(argc, argv, program); + break; + + case AT: + if (argc > optind) { + /* -t and timespec argument are mutually exclusive */ + if (time_set) { + usage(); + exit(EXIT_FAILURE); + } else { + timer = parsetime(argc, argv); + time_set = 1; + } + } + + if (atverify) { + struct tm *tm = localtime(&timer); + (void)fprintf(stderr, "%s\n", asctime(tm)); + } + writefile(timer, queue); + break; + + case BATCH: + if (queue_set) + queue = toupper(queue); + else + queue = DEFAULT_BATCH_QUEUE; + + if (argc > optind) { + /* -t and timespec argument are mutually exclusive */ + if (time_set) { + usage(); + exit(EXIT_FAILURE); + } else { + timer = parsetime(argc, argv); + time_set = 1; + } + } else if (!time_set) + timer = time(NULL); + + if (atverify) { + struct tm *tm = localtime(&timer); + (void)fprintf(stderr, "%s\n", asctime(tm)); + } + + writefile(timer, queue); + break; + + default: + panic("Internal error"); + break; + } + return EXIT_SUCCESS; +} diff --git a/usr.bin/at/at.h b/usr.bin/at/at.h new file mode 100644 index 0000000..64a4a53 --- /dev/null +++ b/usr.bin/at/at.h @@ -0,0 +1,43 @@ +/* $NetBSD: at.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */ + +/* + * at.h - header for at(1) + * Copyright (C) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: $OpenBSD: at.h,v 1.3 1997/03/01 23:40:09 millert Exp $ + */ + +#ifndef _AT_H_ +#define _AT_H_ + +extern bool fcreated; +extern char atfile[]; +extern char atverify; + +#define AT_MAXJOBS 255 /* max jobs outstanding per user */ +#define AT_VERSION 2.9 /* our version number */ + +#define DEFAULT_BATCH_QUEUE 'E' +#define DEFAULT_AT_QUEUE 'c' + +#endif /* _AT_H_ */ diff --git a/usr.bin/at/panic.c b/usr.bin/at/panic.c new file mode 100644 index 0000000..b0b09ca --- /dev/null +++ b/usr.bin/at/panic.c @@ -0,0 +1,110 @@ +/* $NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $ */ + +/* + * panic.c - terminate fast in case of error + * Copyright (c) 1993 by Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* System Headers */ + +#include +#include +#include +#include +#include +#include + +/* Local headers */ + +#include "panic.h" +#include "at.h" +#include "privs.h" + +/* File scope variables */ + +#ifndef lint +#if 0 +static char rcsid[] = "$OpenBSD: panic.c,v 1.4 1997/03/01 23:40:09 millert Exp $"; +#else +__RCSID("$NetBSD: panic.c,v 1.14 2016/03/13 00:32:09 dholland Exp $"); +#endif +#endif + +/* Global functions */ + +__dead +void +panic(const char *a) +{ + + /* + * Something fatal has happened, print error message and exit. + */ + if (fcreated) { + privs_enter(); + (void)unlink(atfile); + privs_exit(); + } + errx(EXIT_FAILURE, "%s", a); +} + +__dead +void +perr(const char *a) +{ + + /* + * Some operating system error; print error message and exit. + */ + perror(a); + if (fcreated) { + privs_enter(); + (void)unlink(atfile); + privs_exit(); + } + exit(EXIT_FAILURE); +} + +__dead +void +privs_fail(const char *msg) +{ + perr(msg); +} + +__dead +void +usage(void) +{ + + /* Print usage and exit. */ + (void)fprintf(stderr, + "usage: at [-bdlmrVv] [-f file] [-q queue] -t [[CC]YY]MMDDhhmm[.SS]\n" + " at [-bdlmrVv] [-f file] [-q queue] time\n" + " at [-V] -c job [job ...]\n" + " atq [-Vv] [-q queue]\n" + " atrm [-V] job [job ...]\n" + " batch [-mVv] [-f file] [-q queue] [-t [[CC]YY]MMDDhhmm[.SS]]\n" + " batch [-mVv] [-f file] [-q queue] [time]\n"); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/at/panic.h b/usr.bin/at/panic.h new file mode 100644 index 0000000..3f470fe --- /dev/null +++ b/usr.bin/at/panic.h @@ -0,0 +1,37 @@ +/* $NetBSD: panic.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */ + +/* + * panic.h - header for at(1) + * Copyright (c) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: $OpenBSD: panic.h,v 1.3 1997/03/01 23:40:10 millert Exp $ + */ + +#ifndef _PANIC_H_ +#define _PANIC_H_ + +void panic(const char *) __dead; +void perr(const char *) __dead; +void usage(void) __dead; + +#endif /* _PANIC_H_ */ diff --git a/usr.bin/at/parsetime.c b/usr.bin/at/parsetime.c new file mode 100644 index 0000000..5b1cc15 --- /dev/null +++ b/usr.bin/at/parsetime.c @@ -0,0 +1,652 @@ +/* $NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $ */ + +/* + * parsetime.c - parse time for at(1) + * Copyright (C) 1993, 1994 Thomas Koenig + * + * modifications for english-language times + * Copyright (C) 1993 David Parsons + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS + * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ + * |NOON | |[TOMORROW] | + * |MIDNIGHT | |[DAY OF WEEK] | + * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| + * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ + */ + +/* System Headers */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local headers */ + +#include "at.h" +#include "panic.h" +#include "parsetime.h" +#include "stime.h" + +/* Structures and unions */ + +typedef enum { /* symbols */ + MIDNIGHT, NOON, TEATIME, + PM, AM, TOMORROW, TODAY, NOW, + MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS, + NUMBER, PLUS, DOT, SLASH, ID, JUNK, + JAN, FEB, MAR, APR, MAY, JUN, + JUL, AUG, SEP, OCT, NOV, DEC, + SUN, MON, TUE, WED, THU, FRI, SAT, + TOKEOF /* EOF marker */ +} tokid_t; + +/* + * parse translation table - table driven parsers can be your FRIEND! + */ +static const struct { + const char *name; /* token name */ + tokid_t value; /* token id */ + bool plural; /* is this plural? */ +} Specials[] = { + {"midnight", MIDNIGHT, false}, /* 00:00:00 of today or tomorrow */ + {"noon", NOON, false}, /* 12:00:00 of today or tomorrow */ + {"teatime", TEATIME, false}, /* 16:00:00 of today or tomorrow */ + {"am", AM, false}, /* morning times for 0-12 clock */ + {"pm", PM, false}, /* evening times for 0-12 clock */ + {"tomorrow", TOMORROW, false}, /* execute 24 hours from time */ + {"today", TODAY, false}, /* execute today - don't advance time */ + {"now", NOW, false}, /* opt prefix for PLUS */ + + {"minute", MINUTES, false}, /* minutes multiplier */ + {"min", MINUTES, false}, + {"m", MINUTES, false}, + {"minutes", MINUTES, true}, /* (pluralized) */ + {"hour", HOURS, false}, /* hours ... */ + {"hr", HOURS, false}, /* abbreviated */ + {"h", HOURS, false}, + {"hours", HOURS, true}, /* (pluralized) */ + {"day", DAYS, false}, /* days ... */ + {"d", DAYS, false}, + {"days", DAYS, true}, /* (pluralized) */ + {"week", WEEKS, false}, /* week ... */ + {"w", WEEKS, false}, + {"weeks", WEEKS, true}, /* (pluralized) */ + { "month", MONTHS, 0 }, /* month ... */ + { "months", MONTHS, 1 }, /* (pluralized) */ + { "year", YEARS, 0 }, /* year ... */ + { "years", YEARS, 1 }, /* (pluralized) */ + {"jan", JAN, false}, + {"feb", FEB, false}, + {"mar", MAR, false}, + {"apr", APR, false}, + {"may", MAY, false}, + {"jun", JUN, false}, + {"jul", JUL, false}, + {"aug", AUG, false}, + {"sep", SEP, false}, + {"oct", OCT, false}, + {"nov", NOV, false}, + {"dec", DEC, false}, + {"january", JAN, false}, + {"february", FEB, false}, + {"march", MAR, false}, + {"april", APR, false}, + {"may", MAY, false}, + {"june", JUN, false}, + {"july", JUL, false}, + {"august", AUG, false}, + {"september", SEP, false}, + {"october", OCT, false}, + {"november", NOV, false}, + {"december", DEC, false}, + {"sunday", SUN, false}, + {"sun", SUN, false}, + {"monday", MON, false}, + {"mon", MON, false}, + {"tuesday", TUE, false}, + {"tue", TUE, false}, + {"wednesday", WED, false}, + {"wed", WED, false}, + {"thursday", THU, false}, + {"thu", THU, false}, + {"friday", FRI, false}, + {"fri", FRI, false}, + {"saturday", SAT, false}, + {"sat", SAT, false} +}; + +/* File scope variables */ + +static char **scp; /* scanner - pointer at arglist */ +static char scc; /* scanner - count of remaining arguments */ +static char *sct; /* scanner - next char pointer in current argument */ +static bool need; /* scanner - need to advance to next argument */ + +static char *sc_token; /* scanner - token buffer */ +static size_t sc_len; /* scanner - length of token buffer */ +static tokid_t sc_tokid;/* scanner - token id */ +static bool sc_tokplur; /* scanner - is token plural? */ + +#ifndef lint +#if 0 +static char rcsid[] = "$OpenBSD: parsetime.c,v 1.4 1997/03/01 23:40:10 millert Exp $"; +#else +__RCSID("$NetBSD: parsetime.c,v 1.19 2009/01/18 01:02:31 lukem Exp $"); +#endif +#endif + +/* Local functions */ +static void assign_date(struct tm *, int, int, int); +static void expect(tokid_t); +static void init_scanner(int, char **); +static void month(struct tm *); +static tokid_t parse_token(char *); +static void plonk(tokid_t) __dead; +static void plus(struct tm *); +static void tod(struct tm *); +static tokid_t token(void); + +/* + * parse a token, checking if it's something special to us + */ +static tokid_t +parse_token(char *arg) +{ + size_t i; + + for (i=0; i < __arraycount(Specials); i++) { + if (strcasecmp(Specials[i].name, arg) == 0) { + sc_tokplur = Specials[i].plural; + return sc_tokid = Specials[i].value; + } + } + + /* not special - must be some random id */ + return ID; +} + +/* + * init_scanner() sets up the scanner to eat arguments + */ +static void +init_scanner(int argc, char **argv) +{ + + scp = argv; + scc = argc; + need = true; + sc_len = 1; + while (argc-- > 0) + sc_len += strlen(*argv++); + + if ((sc_token = malloc(sc_len)) == NULL) + panic("Insufficient virtual memory"); +} + +/* + * token() fetches a token from the input stream + */ +static tokid_t +token(void) +{ + int idx; + + for(;;) { + (void)memset(sc_token, 0, sc_len); + sc_tokid = TOKEOF; + sc_tokplur = false; + idx = 0; + + /* + * if we need to read another argument, walk along the + * argument list; when we fall off the arglist, we'll + * just return TOKEOF forever + */ + if (need) { + if (scc < 1) + return sc_tokid; + sct = *scp; + scp++; + scc--; + need = false; + } + /* + * eat whitespace now - if we walk off the end of the argument, + * we'll continue, which puts us up at the top of the while loop + * to fetch the next argument in + */ + while (isspace((unsigned char)*sct)) + ++sct; + if (!*sct) { + need = true; + continue; + } + + /* + * preserve the first character of the new token + */ + sc_token[0] = *sct++; + + /* + * then see what it is + */ + if (isdigit((unsigned char)sc_token[0])) { + while (isdigit((unsigned char)*sct)) + sc_token[++idx] = *sct++; + sc_token[++idx] = 0; + return sc_tokid = NUMBER; + } else if (isalpha((unsigned char)sc_token[0])) { + while (isalpha((unsigned char)*sct)) + sc_token[++idx] = *sct++; + sc_token[++idx] = 0; + return parse_token(sc_token); + } + else if (sc_token[0] == ':' || sc_token[0] == '.') + return sc_tokid = DOT; + else if (sc_token[0] == '+') + return sc_tokid = PLUS; + else if (sc_token[0] == '/') + return sc_tokid = SLASH; + else + return sc_tokid = JUNK; + } +} + +/* + * plonk() gives an appropriate error message if a token is incorrect + */ +__dead +static void +plonk(tokid_t tok) +{ + + panic(tok == TOKEOF ? "incomplete time" : "garbled time"); +} + +/* + * expect() gets a token and dies most horribly if it's not the token we want + */ +static void +expect(tokid_t desired) +{ + + if (token() != desired) + plonk(sc_tokid); /* and we die here... */ +} + +/* + * plus() parses a now + time + * + * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS] + * + */ +static void +plus(struct tm *tm) +{ + int delay; + int expectplur; + + expect(NUMBER); + + delay = atoi(sc_token); + expectplur = delay != 1; + + switch (token()) { + case YEARS: + tm->tm_year += delay; + break; + case MONTHS: + tm->tm_mon += delay; + break; + case WEEKS: + delay *= 7; + /*FALLTHROUGH*/ + case DAYS: + tm->tm_mday += delay; + break; + case HOURS: + tm->tm_hour += delay; + break; + case MINUTES: + tm->tm_min += delay; + break; + default: + plonk(sc_tokid); + break; + } + + if (expectplur != sc_tokplur) + warnx("pluralization is wrong"); + + tm->tm_isdst = -1; + if (mktime(tm) == -1) + plonk(sc_tokid); +} + +/* + * tod() computes the time of day + * [NUMBER [DOT NUMBER] [AM|PM]] + */ +static void +tod(struct tm *tm) +{ + int hour, minute; + size_t tlen; + + minute = 0; + hour = atoi(sc_token); + tlen = strlen(sc_token); + + /* + * first pick out the time of day - if it's 4 digits, we assume + * a HHMM time, otherwise it's HH DOT MM time + */ + if (token() == DOT) { + expect(NUMBER); + minute = atoi(sc_token); + (void)token(); + } else if (tlen == 4) { + minute = hour % 100; + hour = hour / 100; + } + + if (minute > 59) + panic("garbled time"); + + /* + * check if an AM or PM specifier was given + */ + if (sc_tokid == AM || sc_tokid == PM) { + if (hour > 12) + panic("garbled time"); + + if (sc_tokid == PM) { + if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ + hour += 12; + } else { + if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ + hour = 0; + } + (void)token(); + } else if (hour > 23) + panic("garbled time"); + + /* + * if we specify an absolute time, we don't want to bump the day even + * if we've gone past that time - but if we're specifying a time plus + * a relative offset, it's okay to bump things + */ + if ((sc_tokid == TOKEOF || sc_tokid == PLUS) && tm->tm_hour > hour) { + tm->tm_mday++; + tm->tm_wday++; + } + + tm->tm_hour = hour; + tm->tm_min = minute; +} + +/* + * assign_date() assigns a date, wrapping to next year if needed. + * Accept years in 4-digit, 2-digit, or current year (-1). + */ +static void +assign_date(struct tm *tm, int mday, int mon, int year) +{ + + if (year > 99) { /* four digit year */ + if (year >= TM_YEAR_BASE) + tm->tm_year = year - TM_YEAR_BASE; + else + panic("garbled time"); + } + else if (year >= 0) { /* two digit year */ + tm->tm_year = conv_2dig_year(year) - TM_YEAR_BASE; + } + else if (year == -1) { /* year not given (use default in tm) */ + /* allow for 1 year range from current date */ + if (tm->tm_mon > mon || + (tm->tm_mon == mon && tm->tm_mday > mday)) + tm->tm_year++; + } + else + panic("invalid year"); + + tm->tm_mday = mday; + tm->tm_mon = mon; +} + +/* + * month() picks apart a month specification + * + * /[ NUMBER [NUMBER]] \ + * |[TOMORROW] | + * |[DAY OF WEEK] | + * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| + * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ + */ +static void +month(struct tm *tm) +{ + int year; + int mday, wday, mon; + size_t tlen; + + year = -1; + mday = 0; + switch (sc_tokid) { + case PLUS: + plus(tm); + break; + + case TOMORROW: + /* do something tomorrow */ + tm->tm_mday++; + tm->tm_wday++; + /*FALLTHROUGH*/ + case TODAY: + /* force ourselves to stay in today - no further processing */ + (void)token(); + break; + + case JAN: case FEB: case MAR: case APR: case MAY: case JUN: + case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: + /* + * do month mday [year] + */ + mon = sc_tokid - JAN; + expect(NUMBER); + mday = atoi(sc_token); + if (token() == NUMBER) { + year = atoi(sc_token); + (void)token(); + } + assign_date(tm, mday, mon, year); + break; + + case SUN: case MON: case TUE: + case WED: case THU: case FRI: + case SAT: + /* do a particular day of the week */ + wday = sc_tokid - SUN; + + mday = tm->tm_mday; + + /* if this day is < today, then roll to next week */ + if (wday < tm->tm_wday) + mday += 7 - (tm->tm_wday - wday); + else + mday += (wday - tm->tm_wday); + + tm->tm_wday = wday; + + assign_date(tm, mday, tm->tm_mon, tm->tm_year + TM_YEAR_BASE); + break; + + case NUMBER: + /* + * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy + */ + tlen = strlen(sc_token); + mon = atoi(sc_token); + (void)token(); + + if (sc_tokid == SLASH || sc_tokid == DOT) { + tokid_t sep; + + sep = sc_tokid; + expect(NUMBER); + mday = atoi(sc_token); + if (token() == sep) { + expect(NUMBER); + year = atoi(sc_token); + (void)token(); + } + + /* + * flip months and days for european timing + */ + if (sep == DOT) { + int x = mday; + mday = mon; + mon = x; + } + } else if (tlen == 6 || tlen == 8) { + if (tlen == 8) { + year = (mon % 10000) - 1900; + mon /= 10000; + } else { + year = mon % 100; + mon /= 100; + } + mday = mon % 100; + mon /= 100; + } else + panic("garbled time"); + + mon--; + if (mon < 0 || mon > 11 || mday < 1 || mday > 31) + panic("garbled time"); + + assign_date(tm, mday, mon, year); + break; + default: + /* plonk(sc_tokid); */ /* XXX - die here? */ + break; + } +} + + +/* Global functions */ + +time_t +parsetime(int argc, char **argv) +{ + /* + * Do the argument parsing, die if necessary, and return the + * time the job should be run. + */ + time_t nowtimer, runtimer; + struct tm nowtime, runtime; + int hr = 0; /* this MUST be initialized to zero for + midnight/noon/teatime */ + + nowtimer = time(NULL); + nowtime = *localtime(&nowtimer); + + runtime = nowtime; + runtime.tm_sec = 0; + + if (argc <= optind) + usage(); + + init_scanner(argc - optind, argv + optind); + + switch (token()) { + case NOW: + if (scc < 1) + return nowtimer; + + /* now is optional prefix for PLUS tree */ + expect(PLUS); + /*FALLTHROUGH*/ + case PLUS: + plus(&runtime); + break; + + case NUMBER: + tod(&runtime); + month(&runtime); + break; + + /* + * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised + * hr to zero up above, then fall into this case in such a + * way so we add +12 +4 hours to it for teatime, +12 hours + * to it for noon, and nothing at all for midnight, then + * set our runtime to that hour before leaping into the + * month scanner + */ + case TEATIME: + hr += 4; + /*FALLTHROUGH*/ + case NOON: + hr += 12; + /*FALLTHROUGH*/ + case MIDNIGHT: + if (runtime.tm_hour >= hr) { + runtime.tm_mday++; + runtime.tm_wday++; + } + runtime.tm_hour = hr; + runtime.tm_min = 0; + (void)token(); + /*FALLTHROUGH*/ /* fall through to month setting */ + default: + month(&runtime); + break; + } + expect(TOKEOF); + + /* + * adjust for daylight savings time + */ + runtime.tm_isdst = -1; + runtimer = mktime(&runtime); + + if (runtimer == (time_t)-1) + panic("Invalid time"); + + if (nowtimer > runtimer) + panic("Trying to travel back in time"); + + return runtimer; +} diff --git a/usr.bin/at/parsetime.h b/usr.bin/at/parsetime.h new file mode 100644 index 0000000..a2c9a69 --- /dev/null +++ b/usr.bin/at/parsetime.h @@ -0,0 +1,35 @@ +/* $NetBSD: parsetime.h,v 1.5 2008/04/05 16:26:57 christos Exp $ */ + +/* + * parsetime.h - header for at(1) + * Copyright (c) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: $OpenBSD: parsetime.h,v 1.3 1997/03/01 23:40:11 millert Exp $ + */ + +#ifndef _PARSETIME_H_ +#define _PARSETIME_H_ + +time_t parsetime(int, char **); + +#endif /* _PARSETIME_H_ */ diff --git a/usr.bin/at/pathnames.h b/usr.bin/at/pathnames.h new file mode 100644 index 0000000..88e6a53 --- /dev/null +++ b/usr.bin/at/pathnames.h @@ -0,0 +1,51 @@ +/* $NetBSD: pathnames.h,v 1.8 2008/04/05 16:26:57 christos Exp $ */ + +/* + * Copyright (c) 1993 Christopher G. Demetriou + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the + * NetBSD Project. See http://www.NetBSD.org/ for + * information about NetBSD. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * <> + * + * From: $OpenBSD: pathnames.h,v 1.3 1997/03/01 23:40:11 millert Exp $ + */ + +#ifndef _PATHNAMES_H_ +#define _PATHNAMES_H_ + +#include + +#define _PATH_ATJOBS "/var/at/jobs/" +#define _PATH_ATSPOOL "/var/at/spool/" +#define _PATH_LOCKFILE "/var/at/.lockfile" +#define _PATH_SEQFILE "/var/at/.SEQ" +#define _PATH_AT_ALLOW "/var/at/at.allow" +#define _PATH_AT_DENY "/var/at/at.deny" + +#endif /* _PATHNAMES_H_ */ diff --git a/usr.bin/at/perm.c b/usr.bin/at/perm.c new file mode 100644 index 0000000..64e4fbb --- /dev/null +++ b/usr.bin/at/perm.c @@ -0,0 +1,118 @@ +/* $NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $ */ + +/* + * perm.c - check user permission for at(1) + * Copyright (C) 1994 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* System Headers */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Local headers */ + +#include "at.h" +#include "panic.h" +#include "pathnames.h" +#include "privs.h" +#include "perm.h" + +/* File scope variables */ + +#ifndef lint +#if 0 +static char rcsid[] = "$OpenBSD: perm.c,v 1.1 1997/03/01 23:40:12 millert Exp $"; +#else +__RCSID("$NetBSD: perm.c,v 1.4 2016/03/13 00:32:09 dholland Exp $"); +#endif +#endif + +/* Local functions */ + +static bool +check_for_user(FILE *fp, const char *name) +{ + char *buffer; + size_t len; + bool found = false; + + len = strlen(name); + if ((buffer = malloc(len + 2)) == NULL) + panic("Insufficient virtual memory"); + + while (fgets(buffer, (int)len + 2, fp) != NULL) { + if (strncmp(name, buffer, len) == 0 && buffer[len] == '\n') { + found = true; + break; + } + } + (void)fclose(fp); + free(buffer); + return found; +} + +/* Global functions */ + +bool +check_permission(void) +{ + FILE *fp; + uid_t uid = geteuid(); + struct passwd *pentry; + + if (uid == 0) + return true; + + if ((pentry = getpwuid(uid)) == NULL) { + perror("Cannot access user database"); + exit(EXIT_FAILURE); + } + + privs_enter(); + + fp = fopen(_PATH_AT_ALLOW, "r"); + + privs_exit(); + + if (fp != NULL) { + return check_for_user(fp, pentry->pw_name); + } else { + privs_enter(); + + fp = fopen(_PATH_AT_DENY, "r"); + + privs_exit(); + + if (fp != NULL) + return !check_for_user(fp, pentry->pw_name); + } + return false; +} diff --git a/usr.bin/at/perm.h b/usr.bin/at/perm.h new file mode 100644 index 0000000..89d0178 --- /dev/null +++ b/usr.bin/at/perm.h @@ -0,0 +1,33 @@ +/* $NetBSD: perm.h,v 1.3 2008/04/05 16:26:57 christos Exp $ */ + +/* + * perm.h - header for at(1) + * Copyright (C) 1994 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _PERM_H_ +#define _PERM_H_ + +bool check_permission(void); + +#endif /* _PERM_H_ */ diff --git a/usr.bin/at/privs.c b/usr.bin/at/privs.c new file mode 100644 index 0000000..1b7e12d --- /dev/null +++ b/usr.bin/at/privs.c @@ -0,0 +1,104 @@ +/* $NetBSD: privs.c,v 1.3 2018/02/25 23:48:16 htodd Exp $ */ + +/* + * privs.c - privileged operations + * Copyright (C) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp + */ + +#include + +#include "privs.h" + +/* + * Used by: usr.bin/at + * Used by: libexec/atrun + */ + +/* + * Relinquish privileges temporarily for a setuid or setgid program + * with the option of getting them back later. This is done by + * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once + * at the beginning of the main program. This will cause all operations + * to be executed with the real userid. When you need the privileges + * of the setuid/setgid invocation, call PRIV_START; when you no longer + * need it, call PRIV_END. Note that it is an error to call PRIV_START + * and not PRIV_END within the same function. + * + * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running + * as root, and you want to drop back the effective userid to a + * and the effective group id to b, with the option to get them back + * later. + * + * Problems: Do not use return between PRIV_START and PRIV_END; this + * will cause the program to continue running in an unprivileged + * state. + * + * It is NOT safe to call exec(), system() or popen() with a user- + * supplied program (i.e. without carefully checking PATH and any + * library load paths) with relinquished privileges; the called program + * can acquire them just as easily. Set both effective and real userid + * to the real userid before calling any of them. + */ + +uid_t real_uid, effective_uid; +gid_t real_gid, effective_gid; + +void +privs_enter(void) +{ + if (seteuid(effective_uid) == -1) + privs_fail("Cannot get user privs"); + if (setegid(effective_gid) == -1) + privs_fail("Cannot get group privs"); +} + +void +privs_exit(void) +{ + if (setegid(real_gid) == -1) + privs_fail("Cannot relinquish group privs"); + if (seteuid(real_uid) == -1) + privs_fail("Cannot relinquish user privs"); +} + +void +privs_relinquish(void) +{ + real_uid = getuid(); + effective_uid = geteuid(); + real_gid = getgid(); + effective_gid = getegid(); + privs_exit(); +} + +void +privs_relinquish_root(uid_t ruid, gid_t rgid) +{ + real_uid = ruid; + real_gid = rgid; + effective_uid = geteuid(); + effective_gid = getegid(); + privs_exit(); +} diff --git a/usr.bin/at/privs.h b/usr.bin/at/privs.h new file mode 100644 index 0000000..4344cbe --- /dev/null +++ b/usr.bin/at/privs.h @@ -0,0 +1,76 @@ +/* $NetBSD: privs.h,v 1.10 2016/03/13 00:33:12 dholland Exp $ */ + +/* + * privs.h - header for privileged operations + * Copyright (C) 1993 Thomas Koenig + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. The name of the author(s) may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * From: OpenBSD: privs.h,v 1.4 1997/03/01 23:40:12 millert Exp + */ + +#ifndef _PRIVS_H_ +#define _PRIVS_H_ + +/* + * Used by: usr.bin/at + * Used by: libexec/atrun + */ + +/* + * Relinquish privileges temporarily for a setuid or setgid program + * with the option of getting them back later. This is done by + * using POSIX saved user and groups ids. Call RELINQUISH_PRIVS once + * at the beginning of the main program. This will cause all operations + * to be executed with the real userid. When you need the privileges + * of the setuid/setgid invocation, call PRIV_START; when you no longer + * need it, call PRIV_END. Note that it is an error to call PRIV_START + * and not PRIV_END within the same function. + * + * Use RELINQUISH_PRIVS_ROOT(a,b) if your program started out running + * as root, and you want to drop back the effective userid to a + * and the effective group id to b, with the option to get them back + * later. + * + * Problems: Do not use return between PRIV_START and PRIV_END; this + * will cause the program to continue running in an unprivileged + * state. + * + * It is NOT safe to call exec(), system() or popen() with a user- + * supplied program (i.e. without carefully checking PATH and any + * library load paths) with relinquished privileges; the called program + * can acquire them just as easily. Set both effective and real userid + * to the real userid before calling any of them. + */ + +extern uid_t real_uid, effective_uid; +extern gid_t real_gid, effective_gid; + +void privs_relinquish(void); +void privs_relinquish_root(uid_t ruid, gid_t rgid); + +void privs_enter(void); +void privs_exit(void); + +/* caller provides this */ +__dead void privs_fail(const char *msg); + +#endif /* _PRIV_H_ */ diff --git a/usr.bin/at/stime.c b/usr.bin/at/stime.c new file mode 100644 index 0000000..567b848 --- /dev/null +++ b/usr.bin/at/stime.c @@ -0,0 +1,116 @@ +/* $NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $ */ + +/* + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)touch.c 8.2 (Berkeley) 4/28/95"; +#endif +__RCSID("$NetBSD: stime.c,v 1.7 2008/07/21 14:19:21 lukem Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include "stime.h" + +#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) + +time_t +stime(char *arg) +{ + struct tm *t; + time_t tmptime; + int yearset; + char *p; + /* Start with the current time. */ + if (time(&tmptime) == (time_t)-1) + err(EXIT_FAILURE, "time"); + + if ((t = localtime(&tmptime)) == NULL) + err(EXIT_FAILURE, "localtime"); + /* [[CC]YY]MMDDhhmm[.SS] */ + if ((p = strchr(arg, '.')) == NULL) + t->tm_sec = 0; /* Seconds defaults to 0. */ + else { + if (strlen(p + 1) != 2) + goto terr; + *p++ = '\0'; + t->tm_sec = ATOI2(p); + } + + yearset = 0; + switch (strlen(arg)) { + case 12: /* CCYYMMDDhhmm */ + t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE; + yearset = 1; + /* FALLTHROUGH */ + case 10: /* YYMMDDhhmm */ + if (yearset) { + t->tm_year += ATOI2(arg); + } else { + yearset = ATOI2(arg); + t->tm_year = conv_2dig_year(yearset) - TM_YEAR_BASE; + } + /* FALLTHROUGH */ + case 8: /* MMDDhhmm */ + t->tm_mon = ATOI2(arg); + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + /* FALLTHROUGH */ + case 6: + t->tm_mday = ATOI2(arg); + /* FALLTHROUGH */ + case 4: + t->tm_hour = ATOI2(arg); + /* FALLTHROUGH */ + case 2: + t->tm_min = ATOI2(arg); + break; + default: + goto terr; + } + + t->tm_isdst = -1; /* Figure out DST. */ + tmptime = mktime(t); + if (tmptime == (time_t)-1) { + terr: errx(EXIT_FAILURE, + "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); + } + return tmptime; +} diff --git a/usr.bin/at/stime.h b/usr.bin/at/stime.h new file mode 100644 index 0000000..266953e --- /dev/null +++ b/usr.bin/at/stime.h @@ -0,0 +1,53 @@ +/* $NetBSD: stime.h,v 1.4 2008/04/05 16:26:57 christos Exp $ */ + +/* + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _STIME_H_ +#define _STIME_H_ + +/* + * Convert a 2-digit year to a valid year as: + * 00-68 <-> 2000-2068 + * 69-99 <-> 1969-1999 + * + * Note: We choose the earliest year as 1969 rather than 1970 so that + * dates near the Epoch (Jan 1, 1970 00:00:00 UTC) can be represented + * with 2-digit years in all time zones. This is the convention used + * in parsedate(3), though it is not consistently applied in the + * source tree. + * + * XXX - move this (or something like it) to time.h and use it + * consistently in the tree. + */ +#define conv_2dig_year(y) ((y) < 69 ? (y) + 2000 : (y) + 1900) + +time_t stime(char *); + +#endif /* _STIME_H_ */ diff --git a/usr.bin/basename/basename.1 b/usr.bin/basename/basename.1 new file mode 100644 index 0000000..166b506 --- /dev/null +++ b/usr.bin/basename/basename.1 @@ -0,0 +1,92 @@ +.\" $NetBSD: basename.1,v 1.18 2017/07/03 21:34:18 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)basename.1 8.2 (Berkeley) 4/18/94 +.\" +.Dd April 18, 1994 +.Dt BASENAME 1 +.Os +.Sh NAME +.Nm basename , +.Nm dirname +.Nd return filename or directory portion of pathname +.Sh SYNOPSIS +.Nm +.Ar string +.Op Ar suffix +.Nm dirname +.Ar string +.Sh DESCRIPTION +.Nm +deletes any prefix ending with the last slash +.Ql \&/ +character present in +.Ar string , +and a +.Ar suffix , +if given. +The resulting filename is written to the standard output. +A non-existent suffix is ignored. +.Pp +.Nm dirname +deletes the filename portion, beginning +with the last slash +.Ql \&/ +character to the end of +.Ar string , +and writes the result to the standard output. +.Sh EXIT STATUS +Both the +.Nm +and +.Nm dirname +utilities +exit 0 on success, and >0 if an error occurs. +.Sh EXAMPLES +The following line sets the shell variable +.Ev FOO +to +.Pa /usr/bin . +.Pp +.Dl FOO=`dirname /usr/bin/trail` +.Sh SEE ALSO +.Xr csh 1 , +.Xr sh 1 , +.Xr basename 3 , +.Xr dirname 3 +.Sh STANDARDS +The +.Nm +and +.Nm dirname +utilities conform to +.St -p1003.2-92 . diff --git a/usr.bin/basename/basename.c b/usr.bin/basename/basename.c new file mode 100644 index 0000000..78b5d8b --- /dev/null +++ b/usr.bin/basename/basename.c @@ -0,0 +1,103 @@ +/* $NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)basename.c 8.4 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: basename.c,v 1.16 2019/02/01 08:29:04 mrg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + char *p; + int ch; + + setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc != 1 && argc != 2) + usage(); + + if (**argv == '\0') { + (void)printf("\n"); + exit(0); + } + if ((p = basename(*argv)) == NULL) + err(1, "%s", *argv); + if (*++argv != NULL) { + int suffixlen, stringlen, off; + + suffixlen = strlen(*argv); + stringlen = strlen(p); + + if (suffixlen < stringlen) { + off = stringlen - suffixlen; + if (strcmp(p + off, *argv) == 0) + p[off] = '\0'; + } + } + (void)printf("%s\n", p); + exit(0); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: basename string [suffix]\n"); + exit(1); +} diff --git a/usr.bin/c99/c99.1 b/usr.bin/c99/c99.1 new file mode 100644 index 0000000..0ac95ae --- /dev/null +++ b/usr.bin/c99/c99.1 @@ -0,0 +1,81 @@ +.\" $NetBSD: c99.1,v 1.1 2016/01/14 04:27:26 christos Exp $ +.\" +.\" Copyright (c) 1999-2008 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd September 24, 2008 +.Dt C99 1 +.Os +.Sh NAME +.Nm c99 +.Nd ANSI (1999) C compiler +.Sh SYNOPSIS +.Nm +.Op Fl pedantic +.Op Fl pedantic-errors +.Op Fl D_ANSI_SOURCE +.Op options ... +.Sh DESCRIPTION +Calls the C compiler (cc) with the given +.Ar options , +using a C language environment compatible with the +.St -isoC-99 +specification. +.Pp +This includes +inline functions, variable-length arrays, support for one-line +comments beginning with //, +disabling non-ANSI compiler features (such as +.Ar asm , +.Ar typeof , +and the $ character in identifiers), +and definition of the preprocessor symbol +.Ev __STRICT_ANSI__ . +.Pp +The following options are available: +.Bl -tag -width "-pedantic-errorsxx" +.It Fl pedantic +Issue extra warnings defined by ANSI for use of non-ANSI features. +.It Fl pedantic-errors +Issue errors instead of warnings that normally would be presented by +.Fl pedantic . +.It Fl D_ANSI_SOURCE +Tell the system header file set to use an ANSI-conformant "clean" namespace. +.El +.Sh SEE ALSO +.Xr cc 1 +.Sh STANDARDS +.Nm +conforms to +.St -p1003.2-92 . +.Sh HISTORY +.Nm +first appeared in +.Nx 5.0 . +.Sh BUGS +Since +.Nm +is a shell wrapper script to +.Ar cc , +compile errors are prefixed by "cc:". diff --git a/usr.bin/c99/c99.sh b/usr.bin/c99/c99.sh new file mode 100644 index 0000000..15cab47 --- /dev/null +++ b/usr.bin/c99/c99.sh @@ -0,0 +1,2 @@ +#!/bin/sh +exec /usr/bin/cc -std=c99 "$@" diff --git a/usr.bin/cal/README b/usr.bin/cal/README new file mode 100644 index 0000000..638ac9d --- /dev/null +++ b/usr.bin/cal/README @@ -0,0 +1,42 @@ +The cal(1) date routines were written from scratch, basically from first +principles. The algorithm for calculating the day of week from any +Gregorian date was "reverse engineered". This was necessary as most of +the documented algorithms have to do with date calculations for other +calendars (e.g. julian) and are only accurate when converted to gregorian +within a narrow range of dates. + +1 Jan 1 is a Saturday because that's what cal says and I couldn't change +that even if I was dumb enough to try. From this we can easily calculate +the day of week for any date. The algorithm for a zero based day of week: + + calculate the number of days in all prior years (year-1)*365 + add the number of leap years (days?) since year 1 + (not including this year as that is covered later) + add the day number within the year + this compensates for the non-inclusive leap year + calculation + if the day in question occurs before the gregorian reformation + (3 sep 1752 for our purposes), then simply return + (value so far - 1 + SATURDAY's value of 6) modulo 7. + if the day in question occurs during the reformation (3 sep 1752 + to 13 sep 1752 inclusive) return THURSDAY. This is my + idea of what happened then. It does not matter much as + this program never tries to find day of week for any day + that is not the first of a month. + otherwise, after the reformation, use the same formula as the + days before with the additional step of subtracting the + number of days (11) that were adjusted out of the calendar + just before taking the modulo. + +It must be noted that the number of leap years calculation is sensitive +to the date for which the leap year is being calculated. A year that occurs +before the reformation is determined to be a leap year if its modulo of +4 equals zero. But after the reformation, a year is only a leap year if +its modulo of 4 equals zero and its modulo of 100 does not. Of course, +there is an exception for these century years. If the modulo of 400 equals +zero, then the year is a leap year anyway. This is, in fact, what the +gregorian reformation was all about (a bit of error in the old algorithm +that caused the calendar to be inaccurate.) + +Once we have the day in year for the first of the month in question, the +rest is trivial. diff --git a/usr.bin/cal/cal.1 b/usr.bin/cal/cal.1 new file mode 100644 index 0000000..c99cd5d --- /dev/null +++ b/usr.bin/cal/cal.1 @@ -0,0 +1,164 @@ +.\" $NetBSD: cal.1,v 1.23 2018/05/29 08:37:33 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Kim Letkeman. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)cal.1 8.2 (Berkeley) 4/28/95 +.\" +.Dd May 29, 2018 +.Dt CAL 1 +.Os +.Sh NAME +.Nm cal +.Nd displays a calendar +.Sh SYNOPSIS +.Nm +.Op Fl 3hjry +.Op Fl A Ar after +.Op Fl B Ar before +.Op Fl C Ar context +.Op Fl d Ar day-of-week +.Op Fl R Ar reform-spec +.Op Oo Ar month Oc Ar year +.Sh DESCRIPTION +.Nm +displays a simple calendar. +If arguments are not specified, +the current month is displayed. +The options are as follows: +.Bl -tag -width Ds +.It Fl 3 +Same as +.Dq Fl A Ar 1 Fl B Ar 1 . +.It Fl A Ar after +Display +.Ar after +months after the specified month. +.It Fl B Ar before +Display +.Ar before +months before the specified month. +.It Fl C Ar context +Display +.Ar context +months before and after the specified month. +.It Fl d Ar day-of-week +Specifies the day of the week on which the calendar should start. +Valid values are 0 through 6, presenting Sunday through Saturday, +inclusively. +The default output starts on Sundays. +.It Fl h +Highlight the current day, if present in the displayed calendar. +If output is to a terminal, then the appropriate terminal sequences +are used, otherwise overstriking is used. +If more than one +.Fl h +is used and output is to a terminal, the current date will be +highlighted in inverse video instead of bold. +.It Fl j +Display Julian dates (days one-based, numbered from January 1). +.It Fl R Ar reform-spec +Selects an alternate Gregorian reform point from the default of +September 3rd, 1752. +The +.Ar reform-spec +can be selected by one of the built-in names (see +.Sx NOTES +for a list) or by a date of the form YYYY/MM/DD. +The date and month may be omitted, provided that what is specified +uniquely selects a given built-in reform point. +If an exact date is specified, then that date is taken to be the first +missing date of the Gregorian Reform to be applied. +.It Fl r +Display the month in which the Gregorian Reform adjustment was +applied, if no other +.Ar month +or +.Ar year +information is given. +If used in conjunction with +.Fl y , +then the entire year is displayed. +.It Fl y +Display a calendar for the current year. +.El +.Pp +If no parameters are specified, the current month's calendar is +displayed. +A single parameter specifies the year and optionally the month +in ISO format: +.Dq Li cal 2007-12 +Two parameters denote the month (1 - 12) and year. +Note that the century must be included in the year. +.Pp +A year starts on Jan 1. +.Sh NOTES +In the USA and Great Britain the Gregorian Reformation occurred in 1752. +By this time, most countries had recognized the reformation (although a +few did not recognize it until the 1900's.) +Eleven days following September 2, 1752 were eliminated by the reformation, +so the calendar for that month is a bit unusual. +.Pp +In view of the chaotic way the Gregorian calendar was adopted throughout +the world in the years between 1582 and 1928 make sure to take into account +the date of the Gregorian Reformation in your region if you are checking a +calendar for a very old date. +.Pp +.Nm +has a decent built-in list of Gregorian Reform dates and the names of +the countries where the reform was adopted: +.Bd -literal + Italy Oct. 5, 1582 Denmark Feb. 19, 1700 + Spain Oct. 5, 1582 Great Britain Sep. 3, 1752 + Portugal Oct. 5, 1582 Sweden Feb. 18, 1753 + Poland Oct. 5, 1582 Finland Feb. 18, 1753 + France Dec. 12, 1582 Japan Dec. 20, 1872 + Luxembourg Dec. 22, 1582 China Nov. 7, 1911 + Netherlands Dec. 22, 1582 Bulgaria Apr. 1, 1916 + Bavaria Oct. 6, 1583 U.S.S.R. Feb. 1, 1918 + Austria Jan. 7, 1584 Serbia Jan. 19, 1919 + Switzerland Jan. 12, 1584 Romania Jan. 19, 1919 + Hungary Oct. 22, 1587 Greece Mar. 10, 1924 + Germany Feb. 19, 1700 Turkey Dec. 19, 1925 + Norway Feb. 19, 1700 Egypt Sep. 18, 1928 +.Ed +.Pp +The country known as +.Em Great Britain +can also be referred to as +.Em England +since that has less letters and no spaces in it. +This is meant only as a measure of expediency, not as a possible +slight to anyone involved. +.Sh HISTORY +A +.Nm +command appeared in +.At v1 . diff --git a/usr.bin/cal/cal.c b/usr.bin/cal/cal.c new file mode 100644 index 0000000..712fa59 --- /dev/null +++ b/usr.bin/cal/cal.c @@ -0,0 +1,924 @@ +/* $NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kim Letkeman. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cal.c 8.4 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: cal.c,v 1.29 2015/06/16 22:54:10 christos Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SATURDAY 6 /* 1 Jan 1 was a Saturday */ + +#define FIRST_MISSING_DAY reform->first_missing_day +#define NUMBER_MISSING_DAYS reform->missing_days + +#define MAXDAYS 42 /* max slots in a month array */ +#define SPACE -1 /* used in day array */ + +static int days_in_month[2][13] = { + {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, + {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, +}; + +static int empty[MAXDAYS] = { + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, + SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, SPACE, +}; +static int shift_days[2][4][MAXDAYS + 1]; + +static const char *month_names[12] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +}; + +static const char *day_headings = " S M Tu W Th F S"; +static const char *j_day_headings = " S M Tu W Th F S"; + +/* leap years according to the julian calendar */ +#define j_leap_year(y, m, d) \ + (((m) > 2) && \ + !((y) % 4)) + +/* leap years according to the gregorian calendar */ +#define g_leap_year(y, m, d) \ + (((m) > 2) && \ + ((!((y) % 4) && ((y) % 100)) || \ + !((y) % 400))) + +/* leap year -- account for gregorian reformation at some point */ +#define leap_year(yr) \ + ((yr) <= reform->year ? j_leap_year((yr), 3, 1) : \ + g_leap_year((yr), 3, 1)) + +/* number of julian leap days that have passed by a given date */ +#define j_leap_days(y, m, d) \ + ((((y) - 1) / 4) + j_leap_year(y, m, d)) + +/* number of gregorian leap days that have passed by a given date */ +#define g_leap_days(y, m, d) \ + ((((y) - 1) / 4) - (((y) - 1) / 100) + (((y) - 1) / 400) + \ + g_leap_year(y, m, d)) + +/* + * Subtracting the gregorian leap day count (for a given date) from + * the julian leap day count (for the same date) describes the number + * of days from the date before the shift to the next date that + * appears in the calendar. Since we want to know the number of + * *missing* days, not the number of days that the shift spans, we + * subtract 2. + * + * Alternately... + * + * There's a reason they call the Dark ages the Dark Ages. Part of it + * is that we don't have that many records of that period of time. + * One of the reasons for this is that a lot of the Dark Ages never + * actually took place. At some point in the first millenium A.D., a + * ruler of some power decided that he wanted the number of the year + * to be different than what it was, so he changed it to coincide + * nicely with some event (a birthday or anniversary, perhaps a + * wedding, or maybe a centennial for a largish city). One of the + * side effects of this upon the Gregorian reform is that two Julian + * leap years (leap days celebrated during centennial years that are + * not quatro-centennial years) were skipped. + */ +#define GREGORIAN_MAGIC 2 + +/* number of centuries since the reform, not inclusive */ +#define centuries_since_reform(yr) \ + ((yr) > reform->year ? ((yr) / 100) - (reform->year / 100) : 0) + +/* number of centuries since the reform whose modulo of 400 is 0 */ +#define quad_centuries_since_reform(yr) \ + ((yr) > reform->year ? ((yr) / 400) - (reform->year / 400) : 0) + +/* number of leap years between year 1 and this year, not inclusive */ +#define leap_years_since_year_1(yr) \ + ((yr) / 4 - centuries_since_reform(yr) + quad_centuries_since_reform(yr)) + +static struct reform { + const char *country; + int ambiguity, year, month, date; + long first_missing_day; + int missing_days; + /* + * That's 2 for standard/julian display, 4 for months possibly + * affected by the Gregorian shift, and MAXDAYS + 1 for the + * days that get displayed, plus a crib slot. + */ +} *reform, reforms[] = { + { "DEFAULT", 0, 1752, 9, 3, 0, 0 }, + { "Italy", 1, 1582, 10, 5, 0, 0 }, + { "Spain", 1, 1582, 10, 5, 0, 0 }, + { "Portugal", 1, 1582, 10, 5, 0, 0 }, + { "Poland", 1, 1582, 10, 5, 0, 0 }, + { "France", 2, 1582, 12, 10, 0, 0 }, + { "Luxembourg", 2, 1582, 12, 22, 0, 0 }, + { "Netherlands", 2, 1582, 12, 22, 0, 0 }, + { "Bavaria", 0, 1583, 10, 6, 0, 0 }, + { "Austria", 2, 1584, 1, 7, 0, 0 }, + { "Switzerland", 2, 1584, 1, 12, 0, 0 }, + { "Hungary", 0, 1587, 10, 22, 0, 0 }, + { "Germany", 0, 1700, 2, 19, 0, 0 }, + { "Norway", 0, 1700, 2, 19, 0, 0 }, + { "Denmark", 0, 1700, 2, 19, 0, 0 }, + { "Great Britain", 0, 1752, 9, 3, 0, 0 }, + { "England", 0, 1752, 9, 3, 0, 0 }, + { "America", 0, 1752, 9, 3, 0, 0 }, + { "Sweden", 0, 1753, 2, 18, 0, 0 }, + { "Finland", 0, 1753, 2, 18, 0, 0 }, + { "Japan", 0, 1872, 12, 20, 0, 0 }, + { "China", 0, 1911, 11, 7, 0, 0 }, + { "Bulgaria", 0, 1916, 4, 1, 0, 0 }, + { "U.S.S.R.", 0, 1918, 2, 1, 0, 0 }, + { "Serbia", 0, 1919, 1, 19, 0, 0 }, + { "Romania", 0, 1919, 1, 19, 0, 0 }, + { "Greece", 0, 1924, 3, 10, 0, 0 }, + { "Turkey", 0, 1925, 12, 19, 0, 0 }, + { "Egypt", 0, 1928, 9, 18, 0, 0 }, + { NULL, 0, 0, 0, 0, 0, 0 }, +}; + +static int julian; +static int dow; +static int hilite; +static const char *md, *me; + +static void init_hilite(void); +static int getnum(const char *); +static void gregorian_reform(const char *); +static void reform_day_array(int, int, int *, int *, int *,int *,int *,int *); +static int ascii_day(char *, int); +static void center(const char *, int, int); +static void day_array(int, int, int *); +static int day_in_week(int, int, int); +static int day_in_year(int, int, int); +static void monthrange(int, int, int, int, int); +static void trim_trailing_spaces(char *); +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + struct tm *local_time; + time_t now; + int ch, yflag; + long month, year; + int before, after, use_reform; + int yearly = 0; + char *when, *eoi; + + before = after = 0; + use_reform = yflag = year = 0; + when = NULL; + while ((ch = getopt(argc, argv, "A:B:C:d:hjR:ry3")) != -1) { + switch (ch) { + case 'A': + after = getnum(optarg); + if (after < 0) + errx(1, "Argument to -A must be positive"); + break; + case 'B': + before = getnum(optarg); + if (before < 0) + errx(1, "Argument to -B must be positive"); + break; + case 'C': + after = before = getnum(optarg); + if (after < 0) + errx(1, "Argument to -C must be positive"); + break; + case 'd': + dow = getnum(optarg); + if (dow < 0 || dow > 6) + errx(1, "illegal day of week value: use 0-6"); + break; + case 'h': + init_hilite(); + break; + case 'j': + julian = 1; + break; + case 'R': + when = optarg; + break; + case 'r': + use_reform = 1; + break; + case 'y': + yflag = 1; + break; + case '3': + before = after = 1; + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + + if (when != NULL) + gregorian_reform(when); + if (reform == NULL) + gregorian_reform("DEFAULT"); + + month = 0; + switch (argc) { + case 2: + month = strtol(*argv++, &eoi, 10); + if (month < 1 || month > 12 || *eoi != '\0') + errx(1, "illegal month value: use 1-12"); + year = strtol(*argv, &eoi, 10); + if (year < 1 || year > 9999 || *eoi != '\0') + errx(1, "illegal year value: use 1-9999"); + break; + case 1: + year = strtol(*argv, &eoi, 10); + if (year < 1 || year > 9999 || (*eoi != '\0' && *eoi != '/' && *eoi != '-')) + errx(1, "illegal year value: use 1-9999"); + if (*eoi != '\0') { + month = strtol(eoi + 1, &eoi, 10); + if (month < 1 || month > 12 || *eoi != '\0') + errx(1, "illegal month value: use 1-12"); + } + break; + case 0: + (void)time(&now); + local_time = localtime(&now); + if (use_reform) + year = reform->year; + else + year = local_time->tm_year + TM_YEAR_BASE; + if (!yflag) { + if (use_reform) + month = reform->month; + else + month = local_time->tm_mon + 1; + } + break; + default: + usage(); + } + + if (!month) { + /* yearly */ + month = 1; + before = 0; + after = 11; + yearly = 1; + } + + monthrange(month, year, before, after, yearly); + + exit(0); +} + +#define DAY_LEN 3 /* 3 spaces per day */ +#define J_DAY_LEN 4 /* 4 spaces per day */ +#define WEEK_LEN 20 /* 7 * 3 - one space at the end */ +#define J_WEEK_LEN 27 /* 7 * 4 - one space at the end */ +#define HEAD_SEP 2 /* spaces between day headings */ +#define J_HEAD_SEP 2 +#define MONTH_PER_ROW 3 /* how many monthes in a row */ +#define J_MONTH_PER_ROW 2 + +static void +monthrange(int month, int year, int before, int after, int yearly) +{ + int startmonth, startyear; + int endmonth, endyear; + int i, row; + int days[3][MAXDAYS]; + char lineout[256]; + int inayear; + int newyear; + int day_len, week_len, head_sep; + int month_per_row; + int skip, r_off, w_off; + + if (julian) { + day_len = J_DAY_LEN; + week_len = J_WEEK_LEN; + head_sep = J_HEAD_SEP; + month_per_row = J_MONTH_PER_ROW; + } + else { + day_len = DAY_LEN; + week_len = WEEK_LEN; + head_sep = HEAD_SEP; + month_per_row = MONTH_PER_ROW; + } + + month--; + + startyear = year - (before + 12 - 1 - month) / 12; + startmonth = 12 - 1 - ((before + 12 - 1 - month) % 12); + endyear = year + (month + after) / 12; + endmonth = (month + after) % 12; + + if (startyear < 0 || endyear > 9999) { + errx(1, "year should be in 1-9999"); + } + + year = startyear; + month = startmonth; + inayear = newyear = (year != endyear || yearly); + if (inayear) { + skip = month % month_per_row; + month -= skip; + } + else { + skip = 0; + } + + do { + if (newyear) { + (void)snprintf(lineout, sizeof(lineout), "%d", year); + center(lineout, week_len * month_per_row + + head_sep * (month_per_row - 1), 0); + (void)printf("\n\n"); + newyear = 0; + } + + for (i = 0; i < skip; i++) + center("", week_len, head_sep); + + for (; i < month_per_row; i++) { + int sep; + + if (year == endyear && month + i > endmonth) + break; + + sep = (i == month_per_row - 1) ? 0 : head_sep; + day_array(month + i + 1, year, days[i]); + if (inayear) { + center(month_names[month + i], week_len, sep); + } + else { + snprintf(lineout, sizeof(lineout), "%s %d", + month_names[month + i], year); + center(lineout, week_len, sep); + } + } + printf("\n"); + + for (i = 0; i < skip; i++) + center("", week_len, head_sep); + + for (; i < month_per_row; i++) { + int sep; + + if (year == endyear && month + i > endmonth) + break; + + sep = (i == month_per_row - 1) ? 0 : head_sep; + if (dow) { + printf("%s ", (julian) ? + j_day_headings + 4 * dow : + day_headings + 3 * dow); + printf("%.*s", dow * (julian ? 4 : 3) - 1, + (julian) ? j_day_headings : day_headings); + } else + printf("%s", (julian) ? j_day_headings : day_headings); + printf("%*s", sep, ""); + } + printf("\n"); + + for (row = 0; row < 6; row++) { + char *p = NULL; + + memset(lineout, ' ', sizeof(lineout)); + for (i = 0; i < skip; i++) { + /* nothing */ + } + w_off = 0; + for (; i < month_per_row; i++) { + int col, *dp; + + if (year == endyear && month + i > endmonth) + break; + + p = lineout + i * (week_len + 2) + w_off; + dp = &days[i][row * 7]; + for (col = 0; col < 7; + col++, p += day_len + r_off) { + r_off = ascii_day(p, *dp++); + w_off += r_off; + } + } + *p = '\0'; + trim_trailing_spaces(lineout); + (void)printf("%s\n", lineout); + } + + skip = 0; + month += month_per_row; + if (month >= 12) { + month -= 12; + year++; + newyear = 1; + } + } while (year < endyear || (year == endyear && month <= endmonth)); +} + +/* + * day_array -- + * Fill in an array of 42 integers with a calendar. Assume for a moment + * that you took the (maximum) 6 rows in a calendar and stretched them + * out end to end. You would have 42 numbers or spaces. This routine + * builds that array for any month from Jan. 1 through Dec. 9999. + */ +static void +day_array(int month, int year, int *days) +{ + int day, dw, dm; + time_t t; + struct tm *tm; + + t = time(NULL); + tm = localtime(&t); + tm->tm_year += TM_YEAR_BASE; + tm->tm_mon++; + tm->tm_yday++; /* jan 1 is 1 for us, not 0 */ + + for (dm = month + year * 12, dw = 0; dw < 4; dw++) { + if (dm == shift_days[julian][dw][MAXDAYS]) { + memmove(days, shift_days[julian][dw], + MAXDAYS * sizeof(int)); + return; + } + } + + memmove(days, empty, MAXDAYS * sizeof(int)); + dm = days_in_month[leap_year(year)][month]; + dw = day_in_week(1, month, year); + day = julian ? day_in_year(1, month, year) : 1; + while (dm--) { + if (hilite && year == tm->tm_year && + (julian ? (day == tm->tm_yday) : + (month == tm->tm_mon && day == tm->tm_mday))) + days[dw++] = SPACE - day++; + else + days[dw++] = day++; + } +} + +/* + * day_in_year -- + * return the 1 based day number within the year + */ +static int +day_in_year(int day, int month, int year) +{ + int i, leap; + + leap = leap_year(year); + for (i = 1; i < month; i++) + day += days_in_month[leap][i]; + return (day); +} + +/* + * day_in_week + * return the 0 based day number for any date from 1 Jan. 1 to + * 31 Dec. 9999. Returns the day of the week of the first + * missing day for any given Gregorian shift. + */ +static int +day_in_week(int day, int month, int year) +{ + long temp; + + temp = (long)(year - 1) * 365 + leap_years_since_year_1(year - 1) + + day_in_year(day, month, year); + if (temp < FIRST_MISSING_DAY) + return ((temp - dow + 6 + SATURDAY) % 7); + if (temp >= (FIRST_MISSING_DAY + NUMBER_MISSING_DAYS)) + return (((temp - dow + 6 + SATURDAY) - NUMBER_MISSING_DAYS) % 7); + return ((FIRST_MISSING_DAY - dow + 6 + SATURDAY) % 7); +} + +static int +ascii_day(char *p, int day) +{ + int display, val, rc; + char *b; + static const char *aday[] = { + "", + " 1", " 2", " 3", " 4", " 5", " 6", " 7", + " 8", " 9", "10", "11", "12", "13", "14", + "15", "16", "17", "18", "19", "20", "21", + "22", "23", "24", "25", "26", "27", "28", + "29", "30", "31", + }; + + if (day == SPACE) { + memset(p, ' ', julian ? J_DAY_LEN : DAY_LEN); + return (0); + } + if (day < SPACE) { + b = p; + day = SPACE - day; + } else + b = NULL; + if (julian) { + if ((val = day / 100) != 0) { + day %= 100; + *p++ = val + '0'; + display = 1; + } else { + *p++ = ' '; + display = 0; + } + val = day / 10; + if (val || display) + *p++ = val + '0'; + else + *p++ = ' '; + *p++ = day % 10 + '0'; + } else { + *p++ = aday[day][0]; + *p++ = aday[day][1]; + } + + rc = 0; + if (b != NULL) { + const char *t; + char h[64]; + int l; + + l = p - b; + memcpy(h, b, l); + p = b; + + if (md != NULL) { + for (t = md; *t; rc++) + *p++ = *t++; + memcpy(p, h, l); + p += l; + for (t = me; *t; rc++) + *p++ = *t++; + } else { + for (t = &h[0]; l--; t++) { + *p++ = *t; + rc++; + *p++ = '\b'; + rc++; + *p++ = *t; + } + } + } + + *p = ' '; + return (rc); +} + +static void +trim_trailing_spaces(char *s) +{ + char *p; + + for (p = s; *p; ++p) + continue; + while (p > s && isspace((unsigned char)*--p)) + continue; + if (p > s) + ++p; + *p = '\0'; +} + +static void +center(const char *str, int len, int separate) +{ + + len -= strlen(str); + (void)printf("%*s%s%*s", len / 2, "", str, len / 2 + len % 2, ""); + if (separate) + (void)printf("%*s", separate, ""); +} + +/* + * gregorian_reform -- + * Given a description of date on which the Gregorian Reform was + * applied. The argument can be any of the "country" names + * listed in the reforms array (case insensitive) or a date of + * the form YYYY/MM/DD. The date and month can be omitted if + * doing so would not select more than one different built-in + * reform point. + */ +static void +gregorian_reform(const char *p) +{ + int year, month, date; + int i, days, diw, diy; + char c; + + i = sscanf(p, "%d%*[/,-]%d%*[/,-]%d%c", &year, &month, &date, &c); + switch (i) { + case 4: + /* + * If the character was sscanf()ed, then there's more + * stuff than we need. + */ + errx(1, "date specifier %s invalid", p); + case 0: + /* + * Not a form we can sscanf(), so void these, and we + * can try matching "country" names later. + */ + year = month = date = -1; + break; + case 1: + month = 0; + /*FALLTHROUGH*/ + case 2: + date = 0; + /*FALLTHROUGH*/ + case 3: + /* + * At last, some sanity checking on the values we were + * given. + */ + if (year < 1 || year > 9999) + errx(1, "%d: illegal year value: use 1-9999", year); + if (i > 1 && (month < 1 || month > 12)) + errx(1, "%d: illegal month value: use 1-12", month); + if ((i == 3 && date < 1) || date < 0 || + date > days_in_month[1][month]) + /* + * What about someone specifying a leap day in + * a non-leap year? Well...that's a tricky + * one. We can't yet *say* whether the year + * in question is a leap year. What if the + * date given was, for example, 1700/2/29? is + * that a valid leap day? + * + * So...we punt, and hope that saying 29 in + * the case of February isn't too bad an idea. + */ + errx(1, "%d: illegal date value: use 1-%d", date, + days_in_month[1][month]); + break; + } + + /* + * A complete date was specified, so use the other pope. + */ + if (date > 0) { + static struct reform Goestheveezl; + + reform = &Goestheveezl; + reform->country = "Bompzidaize"; + reform->year = year; + reform->month = month; + reform->date = date; + } + + /* + * No date information was specified, so let's try to match on + * country name. + */ + else if (year == -1) { + for (reform = &reforms[0]; reform->year; reform++) { + if (strcasecmp(p, reform->country) == 0) + break; + } + } + + /* + * We have *some* date information, but not a complete date. + * Let's see if we have enough to pick a single entry from the + * list that's not ambiguous. + */ + else { + for (reform = &reforms[0]; reform->year; reform++) { + if ((year == 0 || year == reform->year) && + (month == 0 || month == reform->month) && + (date == 0 || month == reform->date)) + break; + } + + if (i <= reform->ambiguity) + errx(1, "%s: ambiguous short reform date specification", p); + } + + /* + * Oops...we reached the end of the list. + */ + if (reform->year == 0) + errx(1, "reform name %s invalid", p); + + /* + * + */ + reform->missing_days = + j_leap_days(reform->year, reform->month, reform->date) - + g_leap_days(reform->year, reform->month, reform->date) - + GREGORIAN_MAGIC; + + reform->first_missing_day = + (reform->year - 1) * 365 + + day_in_year(reform->date, reform->month, reform->year) + + date + + j_leap_days(reform->year, reform->month, reform->date); + + /* + * Once we know the day of the week of the first missing day, + * skip back to the first of the month's day of the week. + */ + diw = day_in_week(reform->date, reform->month, reform->year); + diw = (diw + 8 - (reform->date % 7)) % 7; + diy = day_in_year(1, reform->month, reform->year); + + /* + * We might need all four of these (if you switch from Julian + * to Gregorian at some point after 9900, you get a gap of 73 + * days, and that can affect four months), and it doesn't hurt + * all that much to precompute them, so there. + */ + date = 1; + days = 0; + for (i = 0; i < 4; i++) + reform_day_array(reform->month + i, reform->year, + &days, &date, &diw, &diy, + shift_days[0][i], + shift_days[1][i]); +} + +/* + * reform_day_array -- + * Pre-calculates the given month's calendar (in both "standard" + * and "julian day" representations) with respect for days + * skipped during a reform period. + */ +static void +reform_day_array(int month, int year, int *done, int *date, int *diw, int *diy, + int *scal, int *jcal) +{ + int mdays; + + /* + * If the reform was in the month of october or later, then + * the month number from the caller could "overflow". + */ + if (month > 12) { + month -= 12; + year++; + } + + /* + * Erase months, and set crib number. The crib number is used + * later to determine if the month to be displayed is here or + * should be built on the fly with the generic routine + */ + memmove(scal, empty, MAXDAYS * sizeof(int)); + scal[MAXDAYS] = month + year * 12; + memmove(jcal, empty, MAXDAYS * sizeof(int)); + jcal[MAXDAYS] = month + year * 12; + + /* + * It doesn't matter what the actual month is when figuring + * out if this is a leap year or not, just so long as February + * gets the right number of days in it. + */ + mdays = days_in_month[g_leap_year(year, 3, 1)][month]; + + /* + * Bounce back to the first "row" in the day array, and fill + * in any days that actually occur. + */ + for (*diw %= 7; (*date - *done) <= mdays; (*date)++, (*diy)++) { + /* + * "date" doesn't get reset by the caller across calls + * to this routine, so we can actually tell that we're + * looking at April the 41st. Much easier than trying + * to calculate the absolute julian day for a given + * date and then checking that. + */ + if (*date < reform->date || + *date >= reform->date + reform->missing_days) { + scal[*diw] = *date - *done; + jcal[*diw] = *diy; + (*diw)++; + } + } + *done += mdays; +} + +static int +getnum(const char *p) +{ + unsigned long result; + char *ep; + + errno = 0; + result = strtoul(p, &ep, 10); + if (p[0] == '\0' || *ep != '\0') + goto error; + if (errno == ERANGE && result == ULONG_MAX) + goto error; + if (result > INT_MAX) + goto error; + + return (int)result; + +error: + errx(1, "bad number: %s", p); + /*NOTREACHED*/ +} + +static void +init_hilite(void) +{ + const char *term; + int errret; + + hilite++; + + if (!isatty(fileno(stdout))) + return; + + term = getenv("TERM"); + if (term == NULL) + term = "dumb"; + if (setupterm(term, fileno(stdout), &errret) != 0 && errret != 1) + return; + + if (hilite > 1) + md = enter_reverse_mode; + else + md = enter_bold_mode; + me = exit_attribute_mode; + if (me == NULL || md == NULL) + md = me = NULL; +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "usage: cal [-3hjry] [-A after] [-B before] [-C context] [-d day-of-week] " + "[-R reform-spec]\n [[month] year]\n"); + exit(1); +} diff --git a/usr.bin/cksum/cksum.1 b/usr.bin/cksum/cksum.1 new file mode 100644 index 0000000..57c63b1 --- /dev/null +++ b/usr.bin/cksum/cksum.1 @@ -0,0 +1,341 @@ +.\" $NetBSD: cksum.1,v 1.48 2017/07/03 21:34:18 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)cksum.1 8.2 (Berkeley) 4/28/95 +.\" +.Dd August 31, 2014 +.Dt CKSUM 1 +.Os +.Sh NAME +.Nm cksum , +.Nm md2 , +.Nm md4 , +.Nm md5 , +.Nm rmd160 , +.Nm sha1 , +.Nm sum +.Nd display file checksums and block counts +.Sh SYNOPSIS +.Nm cksum +.Op Fl n +.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc +.Op Fl o Ar 1 Ns | Ns Ar 2 +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm sum +.Op Fl n +.Op Fl a Ar algorithm Oo Fl pqtx Oc Oo Fl s Ar string Oc +.Op Fl o Ar 1 Ns | Ns Ar 2 +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm md2 +.Op Fl npqtx +.Op Fl s Ar string +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm md4 +.Op Fl npqtx +.Op Fl s Ar string +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm md5 +.Op Fl npqtx +.Op Fl s Ar string +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm rmd160 +.Op Fl npqtx +.Op Fl s Ar string +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Nm sha1 +.Op Fl npqtx +.Op Fl s Ar string +.Op Ar Li \&| Fl c Oo Fl w Oc Oo Ar sumfile Oc +.Sh DESCRIPTION +The +.Nm +utility writes to the standard output three whitespace separated +fields for each input file. +These fields are a checksum +.Tn CRC , +the total number of octets in the file and the file name. +If no file name is specified, the standard input is used and no file name +is written. +.Pp +The +.Nm sum +utility is identical to the +.Nm +utility, except that it defaults to using historic algorithm 1, as +described below. +It is provided for compatibility only. +.Pp +The +.Nm md2 , +.Nm md4 , +.Nm md5 , +.Nm sha1 , +and +.Nm rmd160 +utilities compute cryptographic hash functions, and write to standard +output the hexadecimal representation of the hash of their input. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl a Ar algorithm +When invoked as +.Nm cksum , +use the specified +.Ar algorithm . +Valid algorithms are: +.Bl -column -offset indent ".Sy Algorithm" ".Sy Bits" ".Sy Description" +.It Sy Algorithm Ta Sy Bits Ta Sy Description +.It Li CRC Ta 32 Ta Default CRC algorithm +.It Li MD2 Ta 128 Ta MD2, per Li RFC1319 +.It Li MD4 Ta 128 Ta MD4, per Li RFC1320 +.It Li MD5 Ta 128 Ta MD5, per Li RFC1321 +.It Li RMD160 Ta 160 Ta RIPEMD-160 +.It Li SHA1 Ta 160 Ta SHA-1, per Li FIPS PUB 180-1 +.It Li SHA256 Ta 256 Ta SHA-2 +.It Li SHA384 Ta 384 Ta SHA-2 +.It Li SHA512 Ta 512 Ta SHA-2 +.It Li old1 Ta 16 Ta Algorithm 1, per Fl o Ar 1 +.It Li old2 Ta 16 Ta Algorithm 2, per Fl o Ar 2 +.El +.It Fl c Op Ar sumfile +Verify (check) files against a list of checksums. +The list is read from +.Ar sumfile , +or from stdin if no filename is given. +E.g. first run +.Dl Ic md5 *.tgz > MD5 +.Dl Ic sha1 *.tgz > SHA1 +to generate a list of MD5 checksums in +.Pa MD5 , +then use the following command to verify them: +.Dl Ic cat MD5 SHA1 | cksum -c +If an error is found during checksum verification, an error +message is printed, and the program returns an error code of 1. +.It Fl o +Use historic algorithms instead of the (superior) default one. +.Pp +Algorithm 1 is the algorithm used by historic +.Bx +systems as the +.Xr sum 1 +algorithm and by historic +.At V +systems as the +.Xr sum 1 +algorithm when using the +.Fl r +option. +This is a 16-bit checksum, with a right rotation before each addition; +overflow is discarded. +.Pp +Algorithm 2 is the algorithm used by historic +.At V +systems as the +default +.Xr sum 1 +algorithm. +This is a 32-bit checksum, and is defined as follows: +.Bd -unfilled -offset indent +s = sum of all bytes; +r = s % 2^16 + (s % 2^32) / 2^16; +cksum = (r % 2^16) + r / 2^16; +.Ed +.Pp +Both algorithm 1 and 2 write to the standard output the same fields as +the default algorithm except that the size of the file in bytes is +replaced with the size of the file in blocks. +For historic reasons, the block size is 1024 for algorithm 1 and 512 +for algorithm 2. +Partial blocks are rounded up. +.It Fl w +Print warnings about malformed checksum files when verifying +checksums with +.Fl c . +.El +.Pp +The following options apply only when using the one of the message +digest algorithms: +.Bl -tag -width indent +.It Fl n +Print the hash and the filename in the normal sum output form, with +the hash at the left and the filename following on the right. +.It Fl p +Echo input from standard input to standard output, and append the +selected message digest. +.It Fl q +Quiet mode \(em only the checksum is printed out. +Overrides the +.Fl n +option. +.It Fl s Ar string +Print the hash of the given string +.Ar string . +.It Fl t +Run a built-in message digest time trial. +.It Fl x +Run a built-in message digest test script. +The tests that are run +are supposed to encompass all the various tests in the suites that +accompany the algorithms' descriptions with the exception of the +last test for the SHA-1 algorithm and the RIPEMD-160 algorithm. +The +last test for these is one million copies of the lower letter a. +.El +.Pp +The default +.Tn CRC +used is based on the polynomial used for +.Tn CRC +error checking +in the networking standard +.St -iso8802-3 . +The +.Tn CRC +checksum encoding is defined by the generating polynomial: +.Pp +.Bd -unfilled -offset indent +G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 +.Ed +.Pp +Mathematically, the +.Tn CRC +value corresponding to a given file is defined by +the following procedure: +.Bd -filled -offset indent +The +.Ar n +bits to be evaluated are considered to be the coefficients of a mod 2 +polynomial M(x) of degree +.Ar n Ns \-1 . +These +.Ar n +bits are the bits from the file, with the most significant bit being the most +significant bit of the first octet of the file and the last bit being the least +significant bit of the last octet, padded with zero bits (if necessary) to +achieve an integral number of octets, followed by one or more octets +representing the length of the file as a binary value, least significant octet +first. +The smallest number of octets capable of representing this integer are used. +.Pp +M(x) is multiplied by x^32 (i.e., shifted left 32 bits) and divided by +G(x) using mod 2 division, producing a remainder R(x) of degree \*[Le] 31. +.Pp +The coefficients of R(x) are considered to be a 32-bit sequence. +.Pp +The bit sequence is complemented and the result is the CRC. +.Ed +.Pp +The +.Nm +and +.Nm sum +utilities exit 0 on success, and >0 if an error occurs. +.Sh SEE ALSO +.Xr openssl 1 , +.Xr mtree 8 +.Pp +The default calculation is identical to that given in pseudo-code +in the following +.Tn ACM +article. +.Rs +.%T "Computation of Cyclic Redundancy Checks Via Table Lookup" +.%A Dilip V. Sarwate +.%J "Communications of the ACM" +.%D "August 1988" +.Re +.Rs +.%A R. Rivest +.%T The MD2 Message-Digest Algorithm +.%O RFC 1319 +.Re +.Rs +.%A R. Rivest +.%T The MD4 Message-Digest Algorithm +.%O RFC 1186 and RFC 1320 +.Re +.Rs +.%A R. Rivest +.%T The MD5 Message-Digest Algorithm +.%O RFC 1321 +.Re +.Rs +.%A U.S. DOC/NIST +.%T Secure Hash Standard +.%O FIPS PUB 180-1 +.Re +.Sh STANDARDS +The +.Nm +utility is expected to conform to +.St -p1003.1-2004 . +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 4.4 . +.Nm md5 +was added in +.Nx 1.3 . +The functionality for +.Nm md2 , +.Nm md4 , +.Nm sha1 , +and +.Nm rmd160 +was added in +.Nx 1.6 . +Support for the SHA-2 algorithms +.Po +.Li SHA256 , +.Li SHA384 , +and +.Li SHA512 +.Pc +was added in +.Nx 3.0 . +The functionality to verify checksum stored in a file +.Pq Fl c +first appeared in +.Nx 4.0 . +Quiet mode +.Pq Fl q +was added in +.Nx 7.0 . +.\" .Pp +.\" The +.\" .Nm sum +.\" utility appeared in +.\" .Bx ?.? +.\" and +.\" .At V . diff --git a/usr.bin/cksum/cksum.c b/usr.bin/cksum/cksum.c new file mode 100644 index 0000000..9e7e87a --- /dev/null +++ b/usr.bin/cksum/cksum.c @@ -0,0 +1,551 @@ +/* $NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James W. Williams of NASA Goddard Space Flight Center. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1997 Jason R. Thorpe. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James W. Williams of NASA Goddard Space Flight Center. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__COPYRIGHT) && !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)cksum.c 8.2 (Berkeley) 4/28/95"; +#endif +__RCSID("$NetBSD: cksum.c,v 1.48 2015/06/16 22:54:10 christos Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +#define PRINT_NORMAL 0x01 +#define PRINT_QUIET 0x02 + +typedef char *(*_filefunc)(const char *, char *); + +const struct hash { + const char *progname; + const char *hashname; + void (*stringfunc)(const char *); + void (*timetrialfunc)(void); + void (*testsuitefunc)(void); + void (*filterfunc)(int); + char *(*filefunc)(const char *, char *); +} hashes[] = { + { "md2", "MD2", + MD2String, MD2TimeTrial, MD2TestSuite, + MD2Filter, MD2File }, + { "md4", "MD4", + MD4String, MD4TimeTrial, MD4TestSuite, + MD4Filter, MD4File }, + { "md5", "MD5", + MD5String, MD5TimeTrial, MD5TestSuite, + MD5Filter, MD5File }, + { "rmd160", "RMD160", + RMD160String, RMD160TimeTrial, RMD160TestSuite, + RMD160Filter, (_filefunc) RMD160File }, + { "sha1", "SHA1", + SHA1String, SHA1TimeTrial, SHA1TestSuite, + SHA1Filter, (_filefunc) SHA1File }, + { "sha256", "SHA256", + SHA256_String, SHA256_TimeTrial, SHA256_TestSuite, + SHA256_Filter, (_filefunc) SHA256_File }, + { "sha384", "SHA384", + SHA384_String, SHA384_TimeTrial, SHA384_TestSuite, + SHA384_Filter, (_filefunc) SHA384_File }, + { "sha512", "SHA512", + SHA512_String, SHA512_TimeTrial, SHA512_TestSuite, + SHA512_Filter, (_filefunc) SHA512_File }, + { .progname = NULL, }, +}; + +static int hash_digest_file(char *, const struct hash *, int); +__dead static void requirehash(const char *); +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + int ch, fd, rval, pflag, nohashstdin; + u_int32_t val; + off_t len; + char *fn; + const char *progname; + int (*cfncn) (int, u_int32_t *, off_t *); + void (*pfncn) (char *, u_int32_t, off_t); + const struct hash *hash; + int i, check_warn, do_check; + int print_flags; + + cfncn = NULL; + pfncn = NULL; + pflag = nohashstdin = 0; + check_warn = 0; + do_check = 0; + print_flags = 0; + + setlocale(LC_ALL, ""); + + progname = getprogname(); + + for (hash = hashes; hash->hashname != NULL; hash++) + if (strcmp(progname, hash->progname) == 0) + break; + + if (hash->hashname == NULL) { + hash = NULL; + + if (!strcmp(progname, "sum")) { + cfncn = csum1; + pfncn = psum1; + } else { + cfncn = crc; + pfncn = pcrc; + } + } + + while ((ch = getopt(argc, argv, "a:cno:pqs:twx")) != -1) + switch(ch) { + case 'a': + if (hash) { + warnx("illegal use of -a option"); + usage(); + } + i = 0; + while (hashes[i].hashname != NULL) { + if (!strcasecmp(hashes[i].hashname, optarg)) { + hash = &hashes[i]; + break; + } + i++; + } + if (hash == NULL) { + if (!strcasecmp(optarg, "old1")) { + cfncn = csum1; + pfncn = psum1; + } else if (!strcasecmp(optarg, "old2")) { + cfncn = csum2; + pfncn = psum2; + } else if (!strcasecmp(optarg, "crc")) { + cfncn = crc; + pfncn = pcrc; + } else { + warnx("illegal argument to -a option"); + usage(); + } + } + break; + case 'c': + do_check = 1; + break; + case 'n': + print_flags |= PRINT_NORMAL; + break; + case 'o': + if (hash) { + warnx("%s mutually exclusive with sum", + hash->hashname); + usage(); + } + if (!strcmp(optarg, "1")) { + cfncn = csum1; + pfncn = psum1; + } else if (!strcmp(optarg, "2")) { + cfncn = csum2; + pfncn = psum2; + } else { + warnx("illegal argument to -o option"); + usage(); + } + break; + case 'p': + if (hash == NULL) + requirehash("-p"); + pflag = 1; + break; + case 'q': + print_flags |= PRINT_QUIET; + break; + case 's': + if (hash == NULL) + requirehash("-s"); + nohashstdin = 1; + hash->stringfunc(optarg); + break; + case 't': + if (hash == NULL) + requirehash("-t"); + nohashstdin = 1; + hash->timetrialfunc(); + break; + case 'w': + check_warn = 1; + break; + case 'x': + if (hash == NULL) + requirehash("-x"); + nohashstdin = 1; + hash->testsuitefunc(); + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (do_check) { + /* + * Verify checksums + */ + FILE *f; + char buf[BUFSIZ]; + char *s, *p_filename, *p_cksum; + int l_filename, l_cksum; + char filename[BUFSIZ]; + char cksum[BUFSIZ]; + int ok,cnt,badcnt; + + rval = 0; + cnt = badcnt = 0; + + if (argc == 0) { + f = fdopen(STDIN_FILENO, "r"); + } else { + f = fopen(argv[0], "r"); + } + if (f == NULL) + err(1, "Cannot read %s", + argc>0?argv[0]:"stdin"); + + while(fgets(buf, sizeof(buf), f) != NULL) { + s = strrchr(buf, '\n'); + if (s) + *s = '\0'; + + p_cksum = p_filename = NULL; + + p_filename = strchr(buf, '('); + if (p_filename) { + /* + * Assume 'normal' output if there's a '(' + */ + p_filename += 1; + print_flags &= ~(PRINT_NORMAL); + + p_cksum = strrchr(p_filename, ')'); + if (p_cksum == NULL) { + if (check_warn) + warnx("bogus format: %s. " + "Skipping...", + buf); + rval = 1; + continue; + } + p_cksum += 4; + + l_cksum = strlen(p_cksum); + l_filename = p_cksum - p_filename - 4; + + /* Sanity check, and find proper hash if + * it's not the same as the current program + */ + if (hash == NULL || + strncmp(buf, hash->hashname, + strlen(hash->hashname)) != 0) { + /* + * Search proper hash + */ + const struct hash *nhash; + + for (nhash = hashes ; + nhash->hashname != NULL; + nhash++) + if (strncmp(buf, + nhash->hashname, + strlen(nhash->hashname)) == 0) + break; + + + if (nhash->hashname == NULL) { + if (check_warn) + warnx("unknown hash: %s", + buf); + rval = 1; + continue; + } else { + hash = nhash; + } + } + + } else { + if (hash) { + int nspaces; + + /* + * 'normal' output, no (ck)sum + */ + print_flags |= PRINT_NORMAL; + nspaces = 1; + + p_cksum = buf; + p_filename = strchr(buf, ' '); + if (p_filename == NULL) { + if (check_warn) + warnx("no filename in %s? " + "Skipping...", buf); + rval = 1; + continue; + } + while (isspace((int)*++p_filename)) + nspaces++; + l_filename = strlen(p_filename); + l_cksum = p_filename - buf - nspaces; + } else { + /* + * sum/cksum output format + */ + p_cksum = buf; + s=strchr(p_cksum, ' '); + if (s == NULL) { + if (check_warn) + warnx("bogus format: %s." + " Skipping...", + buf); + rval = 1; + continue; + } + l_cksum = s - p_cksum; + + p_filename = strrchr(buf, ' '); + if (p_filename == NULL) { + if (check_warn) + warnx("no filename in %s?" + " Skipping...", + buf); + rval = 1; + continue; + } + p_filename++; + l_filename = strlen(p_filename); + } + } + + strlcpy(filename, p_filename, l_filename+1); + strlcpy(cksum, p_cksum, l_cksum+1); + + if (hash) { + if (access(filename, R_OK) == 0 + && strcmp(cksum, hash->filefunc(filename, NULL)) == 0) + ok = 1; + else + ok = 0; + } else { + if ((fd = open(filename, O_RDONLY, 0)) < 0) { + if (check_warn) + warn("%s", filename); + rval = 1; + ok = 0; + } else { + if (cfncn(fd, &val, &len)) + ok = 0; + else { + u_int32_t should_val; + + should_val = + strtoul(cksum, NULL, 10); + if (val == should_val) + ok = 1; + else + ok = 0; + } + close(fd); + } + } + + if (! ok) { + if (hash) + printf("(%s) ", hash->hashname); + printf("%s: FAILED\n", filename); + badcnt++; + } + cnt++; + + } + fclose(f); + + if (badcnt > 0) + rval = 1; + + } else { + /* + * Calculate checksums + */ + + fd = STDIN_FILENO; + fn = NULL; + rval = 0; + do { + if (*argv) { + fn = *argv++; + if (hash != NULL) { + if (hash_digest_file(fn, hash, print_flags)) { + warn("%s", fn); + rval = 1; + } + continue; + } + if ((fd = open(fn, O_RDONLY, 0)) < 0) { + warn("%s", fn); + rval = 1; + continue; + } + } else if (hash && !nohashstdin) { + hash->filterfunc(pflag); + } + + if (hash == NULL) { + if (cfncn(fd, &val, &len)) { + warn("%s", fn ? fn : "stdin"); + rval = 1; + } else + pfncn(fn, val, len); + (void)close(fd); + } + } while (*argv); + } + exit(rval); +} + +static int +hash_digest_file(char *fn, const struct hash *hash, int flags) +{ + char *cp; + + cp = hash->filefunc(fn, NULL); + if (cp == NULL) + return 1; + + if (flags & PRINT_QUIET) + printf("%s\n", cp); + else if (flags & PRINT_NORMAL) + printf("%s %s\n", cp, fn); + else + printf("%s (%s) = %s\n", hash->hashname, fn, cp); + + free(cp); + + return 0; +} + +static void +requirehash(const char *flg) +{ + warnx("%s flag requires `-a algorithm'", flg); + usage(); +} + +static void +usage(void) +{ + const char fileargs[] = "[file ... | -c [-w] [sumfile]]"; + const char sumargs[] = "[-n] [-a algorithm [-ptx] [-s string]] [-o 1|2]"; + const char hashargs[] = "[-nptx] [-s string]"; + + (void)fprintf(stderr, "usage: cksum %s\n %s\n", + sumargs, fileargs); + (void)fprintf(stderr, " sum %s\n %s\n", + sumargs, fileargs); + (void)fprintf(stderr, " md2 %s %s\n", hashargs, fileargs); + (void)fprintf(stderr, " md4 %s %s\n", hashargs, fileargs); + (void)fprintf(stderr, " md5 %s %s\n", hashargs, fileargs); + (void)fprintf(stderr, " rmd160 %s %s\n", hashargs, fileargs); + (void)fprintf(stderr, " sha1 %s %s\n", hashargs, fileargs); + exit(1); +} diff --git a/usr.bin/cksum/crc.c b/usr.bin/cksum/crc.c new file mode 100644 index 0000000..b3979db --- /dev/null +++ b/usr.bin/cksum/crc.c @@ -0,0 +1,162 @@ +/* $NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * James W. Williams of NASA Goddard Space Flight Center. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)crc.c 8.1 (Berkeley) 6/17/93"; +#else +__RCSID("$NetBSD: crc.c,v 1.19 2014/10/29 18:09:35 uebayasi Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include "extern.h" + +static const u_int32_t crctab[] = { + 0x0, + 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, + 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, + 0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, + 0x5bd4b01b, 0x569796c2, 0x52568b75, 0x6a1936c8, 0x6ed82b7f, + 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, + 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, + 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, + 0xa4ad16ea, 0xa06c0b5d, 0xd4326d90, 0xd0f37027, 0xddb056fe, + 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95, + 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, + 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, + 0x2ac12072, 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, + 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca, 0x7897ab07, + 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, + 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, + 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, + 0xbb60adfc, 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, + 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, + 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, + 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 0xc6bcf05f, + 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, + 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, + 0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, + 0x58c1663d, 0x558240e4, 0x51435d53, 0x251d3b9e, 0x21dc2629, + 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, + 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, + 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, + 0xeba91bbc, 0xef68060b, 0xd727bbb6, 0xd3e6a601, 0xdea580d8, + 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3, + 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, + 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, + 0x857130c3, 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, + 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c, 0x7b827d21, + 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, + 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 0x18197087, + 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, + 0x2056cd3a, 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, + 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, + 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, + 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 0x89b8fd09, + 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, + 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +/* + * Compute a POSIX 1003.2 checksum. This routine has been broken out so that + * other programs can use it. It takes a file descriptor to read from and + * locations to store the crc and the number of bytes read. It returns 0 on + * success and 1 on failure. Errno is set on failure. + */ +int +crc(int fd, u_int32_t *cval, off_t *clen) +{ + u_char *p; + ssize_t nr; + u_int32_t thecrc; + off_t len; + u_char buf[16 * 1024]; + +#define COMPUTE(var, ch) (var) = (var) << 8 ^ crctab[(var) >> 24 ^ (ch)] + + thecrc = 0; + len = 0; + while ((nr = read(fd, buf, sizeof(buf))) > 0) + for (len += nr, p = buf; nr--; ++p) { + COMPUTE(thecrc, *p); + } + if (nr < 0) + return 1; + + *clen = len; + + /* Include the length of the file. */ + for (; len != 0; len >>= 8) { + COMPUTE(thecrc, len & 0xff); + } + + *cval = ~thecrc; + return 0; +} + +/* These two are rather more useful to the outside world */ + +uint32_t +crc_buf(uint32_t thecrc, const void *buf, size_t len) +{ + const uint8_t *p = buf; + + for (p = buf; len; p++, len--) + COMPUTE(thecrc, *p); + return thecrc; +} + +uint32_t +crc_byte(uint32_t thecrc, unsigned int byte_val) +{ + COMPUTE(thecrc, byte_val & 0xff); + return thecrc; +} diff --git a/usr.bin/cksum/crc_extern.h b/usr.bin/cksum/crc_extern.h new file mode 100644 index 0000000..b0c1c33 --- /dev/null +++ b/usr.bin/cksum/crc_extern.h @@ -0,0 +1,38 @@ +/* $NetBSD: crc_extern.h,v 1.1 2006/09/04 20:01:10 dsl Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +__BEGIN_DECLS +int crc(int, u_int32_t *, off_t *); +uint32_t crc_buf(uint32_t, const void *, size_t); +uint32_t crc_byte(uint32_t, unsigned int); +__END_DECLS diff --git a/usr.bin/cksum/extern.h b/usr.bin/cksum/extern.h new file mode 100644 index 0000000..8384110 --- /dev/null +++ b/usr.bin/cksum/extern.h @@ -0,0 +1,88 @@ +/* $NetBSD: extern.h,v 1.19 2006/09/04 20:01:10 dsl Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#include "crc_extern.h" + +__BEGIN_DECLS +void pcrc(char *, u_int32_t, off_t); +void psum1(char *, u_int32_t, off_t); +void psum2(char *, u_int32_t, off_t); +int csum1(int, u_int32_t *, off_t *); +int csum2(int, u_int32_t *, off_t *); +int md5(int, u_int32_t *, u_int32_t *); + +void MD2String(const char *); +void MD2TimeTrial(void); +void MD2TestSuite(void); +void MD2Filter(int); + +void MD4String(const char *); +void MD4TimeTrial(void); +void MD4TestSuite(void); +void MD4Filter(int); + +void MD5String(const char *); +void MD5TimeTrial(void); +void MD5TestSuite(void); +void MD5Filter(int); + +void SHA1String(const char *); +void SHA1TimeTrial(void); +void SHA1TestSuite(void); +void SHA1Filter(int); + +void RMD160String(const char *); +void RMD160TimeTrial(void); +void RMD160TestSuite(void); +void RMD160Filter(int); + +void SHA256_String(const char *); +void SHA256_TimeTrial(void); +void SHA256_TestSuite(void); +void SHA256_Filter(int); + +void SHA384_String(const char *); +void SHA384_TimeTrial(void); +void SHA384_TestSuite(void); +void SHA384_Filter(int); + +void SHA512_String(const char *); +void SHA512_TimeTrial(void); +void SHA512_TestSuite(void); +void SHA512_Filter(int); +__END_DECLS diff --git a/usr.bin/cksum/md2.c b/usr.bin/cksum/md2.c new file mode 100644 index 0000000..cc80b4a --- /dev/null +++ b/usr.bin/cksum/md2.c @@ -0,0 +1,21 @@ +/* $NetBSD: md2.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */ + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "MD2" +#define HASHLEN 32 + +#define MD5Filter MD2Filter +#define MD5String MD2String +#define MD5TestSuite MD2TestSuite +#define MD5TimeTrial MD2TimeTrial + +#define MD5Data MD2Data +#define MD5Init MD2Init +#define MD5Update MD2Update +#define MD5End MD2End + +#define MD5_CTX MD2_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/md4.c b/usr.bin/cksum/md4.c new file mode 100644 index 0000000..e27b416 --- /dev/null +++ b/usr.bin/cksum/md4.c @@ -0,0 +1,21 @@ +/* $NetBSD: md4.c,v 1.1 2001/03/20 18:46:26 atatat Exp $ */ + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "MD4" +#define HASHLEN 32 + +#define MD5Filter MD4Filter +#define MD5String MD4String +#define MD5TestSuite MD4TestSuite +#define MD5TimeTrial MD4TimeTrial + +#define MD5Data MD4Data +#define MD5Init MD4Init +#define MD5Update MD4Update +#define MD5End MD4End + +#define MD5_CTX MD4_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/md5.c b/usr.bin/cksum/md5.c new file mode 100644 index 0000000..5547771 --- /dev/null +++ b/usr.bin/cksum/md5.c @@ -0,0 +1,153 @@ +/* $NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $ */ + +/* + * MDDRIVER.C - test driver for MD2, MD4 and MD5 + */ + +/* + * Copyright (C) 1990-2, RSA Data Security, Inc. Created 1990. All + * rights reserved. + * + * RSA Data Security, Inc. makes no representations concerning either + * the merchantability of this software or the suitability of this + * software for any particular purpose. It is provided "as is" + * without express or implied warranty of any kind. + * + * These notices must be retained in any copies of any part of this + * documentation and/or software. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +__RCSID("$NetBSD: md5.c,v 1.10 2008/12/29 00:51:29 christos Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include + +void MD5Filter(int); +void MD5String(const char *); +void MD5TestSuite(void); +void MD5TimeTrial(void); + +#ifndef HASHTYPE +#define HASHTYPE "MD5" +#endif + +#ifndef HASHLEN +#define HASHLEN 32 +#endif + +/* + * Length of test block, number of test blocks. + */ +#define TEST_BLOCK_LEN 1000 +#define TEST_BLOCK_COUNT 1000 + +/* + * Digests a string and prints the result. + */ +void +MD5String(const char *string) +{ + unsigned int len = strlen(string); + char buf[HASHLEN + 1]; + + printf("%s (\"%s\") = %s\n", HASHTYPE, string, + MD5Data((const unsigned char *)string, len, buf)); +} + +/* + * Measures the time to digest TEST_BLOCK_COUNT TEST_BLOCK_LEN-byte blocks. + */ +void +MD5TimeTrial(void) +{ + MD5_CTX context; + time_t endTime, startTime; + unsigned char block[TEST_BLOCK_LEN]; + unsigned int i; + char *p, buf[HASHLEN + 1]; + + printf("%s time trial. Digesting %d %d-byte blocks ...", HASHTYPE, + TEST_BLOCK_LEN, TEST_BLOCK_COUNT); + fflush(stdout); + + /* Initialize block */ + for (i = 0; i < TEST_BLOCK_LEN; i++) + block[i] = (unsigned char) (i & 0xff); + + /* Start timer */ + time(&startTime); + + /* Digest blocks */ + MD5Init(&context); + for (i = 0; i < TEST_BLOCK_COUNT; i++) + MD5Update(&context, block, TEST_BLOCK_LEN); + p = MD5End(&context,buf); + + /* Stop timer */ + time(&endTime); + + printf(" done\n"); + printf("Digest = %s\n", p); + printf("Time = %ld seconds\n", (long) (endTime - startTime)); + + /* + * Be careful that endTime-startTime is not zero. + * (Bug fix from Ric * Anderson, ric@Artisoft.COM.) + */ + printf("Speed = %lld bytes/second\n", + (long long) TEST_BLOCK_LEN * TEST_BLOCK_COUNT / + ((endTime - startTime) != 0 ? (endTime - startTime) : 1)); +} + +/* + * Digests a reference suite of strings and prints the results. + */ +void +MD5TestSuite(void) +{ + printf("%s test suite:\n", HASHTYPE); + + MD5String(""); + MD5String("a"); + MD5String("abc"); + MD5String("message digest"); + MD5String("abcdefghijklmnopqrstuvwxyz"); + MD5String("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); + MD5String + ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + MD5String + ("1234567890123456789012345678901234567890\ +1234567890123456789012345678901234567890"); +} + +/* + * Digests the standard input and prints the result. + */ +void +MD5Filter(int pipe) +{ + MD5_CTX context; + size_t len; + unsigned char buffer[BUFSIZ]; + char buf[HASHLEN + 1]; + + MD5Init(&context); + while ((len = fread(buffer, (size_t)1, (size_t)BUFSIZ, stdin)) > 0) { + if (pipe && (len != fwrite(buffer, (size_t)1, len, stdout))) + err(1, "stdout"); + MD5Update(&context, buffer, (unsigned int)len); + } + printf("%s\n", MD5End(&context,buf)); +} diff --git a/usr.bin/cksum/print.c b/usr.bin/cksum/print.c new file mode 100644 index 0000000..03dbd5a --- /dev/null +++ b/usr.bin/cksum/print.c @@ -0,0 +1,76 @@ +/* $NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)print.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: print.c,v 1.11 2005/01/12 17:04:35 xtraeme Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include "extern.h" + +void +pcrc(char *fn, u_int32_t val, off_t len) +{ + (void)printf("%lu %lld", (unsigned long)val, (long long)len); + if (fn) + (void)printf(" %s", fn); + (void)printf("\n"); +} + +void +psum1(char *fn, u_int32_t val, off_t len) +{ + (void)printf("%lu %lld", (unsigned long)val, + (long long)(len + 1023) / 1024); + if (fn) + (void)printf(" %s", fn); + (void)printf("\n"); +} + +void +psum2(char *fn, u_int32_t val, off_t len) +{ + (void)printf("%lu %lld", (unsigned long)val, + (long long)(len + 511) / 512); + if (fn) + (void)printf(" %s", fn); + (void)printf("\n"); +} diff --git a/usr.bin/cksum/rmd160.c b/usr.bin/cksum/rmd160.c new file mode 100644 index 0000000..f04a58d --- /dev/null +++ b/usr.bin/cksum/rmd160.c @@ -0,0 +1,25 @@ +/* $NetBSD: rmd160.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "RMD160" +#define HASHLEN 40 + +#define MD5Filter RMD160Filter +#define MD5String RMD160String +#define MD5TestSuite RMD160TestSuite +#define MD5TimeTrial RMD160TimeTrial + +#define MD5Data RMD160Data +#define MD5Init RMD160Init +#define MD5Update RMD160Update +#define MD5End RMD160End + +#define MD5_CTX RMD160_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/sha1.c b/usr.bin/cksum/sha1.c new file mode 100644 index 0000000..221a6e0 --- /dev/null +++ b/usr.bin/cksum/sha1.c @@ -0,0 +1,21 @@ +/* $NetBSD: sha1.c,v 1.1 2001/03/20 18:46:27 atatat Exp $ */ + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "SHA1" +#define HASHLEN 40 + +#define MD5Filter SHA1Filter +#define MD5String SHA1String +#define MD5TestSuite SHA1TestSuite +#define MD5TimeTrial SHA1TimeTrial + +#define MD5Data SHA1Data +#define MD5Init SHA1Init +#define MD5Update SHA1Update +#define MD5End SHA1End + +#define MD5_CTX SHA1_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/sha256.c b/usr.bin/cksum/sha256.c new file mode 100644 index 0000000..41f272e --- /dev/null +++ b/usr.bin/cksum/sha256.c @@ -0,0 +1,25 @@ +/* $NetBSD: sha256.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "SHA256" +#define HASHLEN 64 + +#define MD5Filter SHA256_Filter +#define MD5String SHA256_String +#define MD5TestSuite SHA256_TestSuite +#define MD5TimeTrial SHA256_TimeTrial + +#define MD5Data SHA256_Data +#define MD5Init SHA256_Init +#define MD5Update SHA256_Update +#define MD5End SHA256_End + +#define MD5_CTX SHA256_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/sha384.c b/usr.bin/cksum/sha384.c new file mode 100644 index 0000000..845988b --- /dev/null +++ b/usr.bin/cksum/sha384.c @@ -0,0 +1,25 @@ +/* $NetBSD: sha384.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "SHA384" +#define HASHLEN 96 + +#define MD5Filter SHA384_Filter +#define MD5String SHA384_String +#define MD5TestSuite SHA384_TestSuite +#define MD5TimeTrial SHA384_TimeTrial + +#define MD5Data SHA384_Data +#define MD5Init SHA384_Init +#define MD5Update SHA384_Update +#define MD5End SHA384_End + +#define MD5_CTX SHA384_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/sha512.c b/usr.bin/cksum/sha512.c new file mode 100644 index 0000000..69dae77 --- /dev/null +++ b/usr.bin/cksum/sha512.c @@ -0,0 +1,25 @@ +/* $NetBSD: sha512.c,v 1.3 2006/10/30 20:22:54 christos Exp $ */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include /* this hash type */ +#include /* the hash we're replacing */ + +#define HASHTYPE "SHA512" +#define HASHLEN 128 + +#define MD5Filter SHA512_Filter +#define MD5String SHA512_String +#define MD5TestSuite SHA512_TestSuite +#define MD5TimeTrial SHA512_TimeTrial + +#define MD5Data SHA512_Data +#define MD5Init SHA512_Init +#define MD5Update SHA512_Update +#define MD5End SHA512_End + +#define MD5_CTX SHA512_CTX + +#include "md5.c" diff --git a/usr.bin/cksum/sum1.c b/usr.bin/cksum/sum1.c new file mode 100644 index 0000000..22e466e --- /dev/null +++ b/usr.bin/cksum/sum1.c @@ -0,0 +1,76 @@ +/* $NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)sum1.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: sum1.c,v 1.13 2005/02/05 00:13:34 simonb Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include "extern.h" + +int +csum1(int fd, u_int32_t *cval, off_t *clen) +{ + off_t total; + int nr; + u_int thecrc; + u_char *p; + u_char buf[8192]; + + /* + * 16-bit checksum, rotating right before each addition; + * overflow is discarded. + */ + thecrc = total = 0; + while ((nr = read(fd, buf, sizeof(buf))) > 0) + for (total += nr, p = buf; nr--; ++p) { + if (thecrc & 1) + thecrc |= 0x10000; + thecrc = ((thecrc >> 1) + *p) & 0xffff; + } + if (nr < 0) + return 1; + + *cval = thecrc; + *clen = total; + return 0; +} diff --git a/usr.bin/cksum/sum2.c b/usr.bin/cksum/sum2.c new file mode 100644 index 0000000..34b3e2e --- /dev/null +++ b/usr.bin/cksum/sum2.c @@ -0,0 +1,80 @@ +/* $NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)sum2.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: sum2.c,v 1.13 2005/02/05 00:13:34 simonb Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include "extern.h" + +int +csum2(int fd, u_int32_t *cval, off_t *clen) +{ + u_int32_t thecrc; + off_t total; + int nr; + u_char *p; + u_char buf[8192]; + + /* + * Draft 8 POSIX 1003.2: + * + * s = sum of all bytes + * r = s % 2^16 + (s % 2^32) / 2^16 + * thecrc = (r % 2^16) + r / 2^16 + */ + thecrc = 0; + total = 0; + while ((nr = read(fd, buf, sizeof(buf))) > 0) + for (total += nr, p = buf; nr--; ++p) + thecrc += *p; + if (nr < 0) + return 1; + + thecrc = (thecrc & 0xffff) + (thecrc >> 16); + thecrc = (thecrc & 0xffff) + (thecrc >> 16); + + *cval = thecrc; + *clen = total; + return 0; +} diff --git a/usr.bin/cmp/cmp.1 b/usr.bin/cmp/cmp.1 new file mode 100644 index 0000000..e61f62c --- /dev/null +++ b/usr.bin/cmp/cmp.1 @@ -0,0 +1,122 @@ +.\" $NetBSD: cmp.1,v 1.13 2017/07/03 21:34:18 wiz Exp $ +.\" +.\" Copyright (c) 1987, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)cmp.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd December 13, 2016 +.Dt CMP 1 +.Os +.Sh NAME +.Nm cmp +.Nd compare two files +.Sh SYNOPSIS +.Nm +.Op Fl c +.Op Fl l | Fl s +.Ar file1 file2 +.Op Ar skip1 Op Ar skip2 +.Sh DESCRIPTION +The cmp utility compares two files of any type and writes the results +to the standard output. +By default, +.Nm +is silent if the files are the same; if they differ, the byte +and line number at which the first difference occurred is reported. +.Pp +Bytes and lines are numbered beginning with one. +.Pp +The following options are available: +.Bl -tag -width flag +.It Fl c +Do the compare using +.Xr getc 3 +rather than +.Xr mmap 2 . +Combined with the +.Fl l +flag, this can be helpful in locating an I/O error in the underlying +device. +.It Fl l +Print the byte number (decimal) and the differing +byte values (octal) for each difference. +.It Fl s +Print nothing for differing files; return exit +status only. +.El +.Pp +The optional arguments +.Ar skip1 +and +.Ar skip2 +are the byte offsets from the beginning of +.Ar file1 +and +.Ar file2 , +respectively, where the comparison will begin. +The offset is decimal by default, but may be expressed as an hexadecimal +or octal value by preceding it with a leading ``0x'' or ``0''. +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width 4n +.It 0 +The files are identical. +.It 1 +The files are different; this includes the case +where one file is identical to the first part of +the other. +In the latter case, if the +.Fl s +option has not been specified, +.Nm +writes to standard output that EOF was reached in the shorter +file (before any differences were found). +.It >1 +An error occurred. +.El +.Sh SEE ALSO +.Xr comm 1 , +.Xr diff 1 , +.Xr diff3 1 , +.Xr sdiff 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +.Sh HISTORY +The +.Nm +utility first appeared in +.At v1 . diff --git a/usr.bin/cmp/cmp.c b/usr.bin/cmp/cmp.c new file mode 100644 index 0000000..4301731 --- /dev/null +++ b/usr.bin/cmp/cmp.c @@ -0,0 +1,168 @@ +/* $NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $ */ + +/* + * Copyright (c) 1987, 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1987, 1990, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cmp.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: cmp.c,v 1.20 2016/10/30 19:33:49 christos Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +int lflag, sflag; + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + struct stat sb1, sb2; + off_t skip1 = 0, skip2 = 0; + int ch, fd1, fd2, special; + const char *file1, *file2; + + setlocale(LC_ALL, ""); + + special = 0; + while ((ch = getopt(argc, argv, "cls")) != -1) + switch (ch) { + case 'c': + special = 1; /* careful! Don't use mmap() */ + break; + case 'l': /* print all differences */ + lflag = 1; + break; + case 's': /* silent run */ + sflag = 1; + break; + case '?': + default: + usage(); + } + argv += optind; + argc -= optind; + + if (lflag && sflag) + errx(ERR_EXIT, "only one of -l and -s may be specified"); + + if (argc < 2 || argc > 4) + usage(); + + /* Backward compatibility -- handle "-" meaning stdin. */ + if (strcmp(file1 = argv[0], "-") == 0) { + special = 1; + fd1 = 0; + file1 = "stdin"; + } + else if ((fd1 = open(file1, O_RDONLY, 0)) < 0) { + if (!sflag) + warn("%s", file1); + exit(ERR_EXIT); + } + if (strcmp(file2 = argv[1], "-") == 0) { + if (special) + errx(ERR_EXIT, + "standard input may only be specified once"); + special = 1; + fd2 = 0; + file2 = "stdin"; + } + else if ((fd2 = open(file2, O_RDONLY, 0)) < 0) { + if (!sflag) + warn("%s", file2); + exit(ERR_EXIT); + } + + if (argc > 2) { + char *ep; + + errno = 0; + skip1 = strtoq(argv[2], &ep, 0); + if (errno || ep == argv[2]) + usage(); + + if (argc == 4) { + skip2 = strtoq(argv[3], &ep, 0); + if (errno || ep == argv[3]) + usage(); + } + } + + if (!special) { + if (fstat(fd1, &sb1)) + err(ERR_EXIT, "%s", file1); + if (!S_ISREG(sb1.st_mode)) + special = 1; + else { + if (fstat(fd2, &sb2)) + err(ERR_EXIT, "%s", file2); + if (!S_ISREG(sb2.st_mode)) + special = 1; + } + } + + if (special) + c_special(fd1, file1, skip1, fd2, file2, skip2); + else + c_regular(fd1, file1, skip1, sb1.st_size, + fd2, file2, skip2, sb2.st_size); + exit(0); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-c] [-l | -s] file1 file2 [skip1 [skip2]]\n", + getprogname()); + exit(ERR_EXIT); +} diff --git a/usr.bin/cmp/extern.h b/usr.bin/cmp/extern.h new file mode 100644 index 0000000..d8a72ed --- /dev/null +++ b/usr.bin/cmp/extern.h @@ -0,0 +1,44 @@ +/* $NetBSD: extern.h,v 1.9 2011/08/29 14:14:11 joerg Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.3 (Berkeley) 4/2/94 + */ + +#define OK_EXIT 0 +#define DIFF_EXIT 1 +#define ERR_EXIT 2 /* error exit code */ + +void c_regular(int, const char *, off_t, off_t, int, const char *, off_t, off_t); +void c_special(int, const char *, off_t, int, const char *, off_t); +__dead void diffmsg(const char *, const char *, off_t, off_t); +__dead void eofmsg(const char *, off_t, off_t); +__dead void errmsg(const char *, off_t, off_t); + +extern int lflag, sflag; diff --git a/usr.bin/cmp/misc.c b/usr.bin/cmp/misc.c new file mode 100644 index 0000000..3195de9 --- /dev/null +++ b/usr.bin/cmp/misc.c @@ -0,0 +1,84 @@ +/* $NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)misc.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: misc.c,v 1.12 2009/04/11 12:16:12 lukem Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include + +#include "extern.h" + +void +errmsg(const char *file, off_t byte, off_t line) +{ + if (lflag) + err(ERR_EXIT, "%s: char %lld, line %lld", file, + (long long)byte, (long long)line); + else + err(ERR_EXIT, "%s", file); +} + +void +eofmsg(const char *file, off_t byte, off_t line) +{ + if (!sflag) { + if (!lflag) + warnx("EOF on %s", file); + else { + if (line > 0) + warnx("EOF on %s: char %lld, line %lld", + file, (long long)byte, (long long)line); + else + warnx("EOF on %s: char %lld", + file, (long long)byte); + } + } + exit(DIFF_EXIT); +} + +void +diffmsg(const char *file1, const char *file2, off_t byte, off_t line) +{ + if (!sflag) + (void)printf("%s %s differ: char %lld, line %lld\n", + file1, file2, (long long)byte, (long long)line); + exit(DIFF_EXIT); +} diff --git a/usr.bin/cmp/regular.c b/usr.bin/cmp/regular.c new file mode 100644 index 0000000..63c663f --- /dev/null +++ b/usr.bin/cmp/regular.c @@ -0,0 +1,118 @@ +/* $NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)regular.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: regular.c,v 1.24 2013/11/20 17:19:14 kleink Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "extern.h" + +void +c_regular(int fd1, const char *file1, off_t skip1, off_t len1, + int fd2, const char *file2, off_t skip2, off_t len2) +{ + u_char ch, *p1, *p2; + off_t byte, length, line; + int dfound; + size_t blk_sz, blk_cnt; + + if (sflag && len1 != len2) + exit(1); + + if (skip1 > len1) + eofmsg(file1, len1 + 1, 0); + len1 -= skip1; + if (skip2 > len2) + eofmsg(file2, len2 + 1, 0); + len2 -= skip2; + + byte = line = 1; + dfound = 0; + length = MIN(len1, len2); + for (blk_sz = 1024 * 1024; length != 0; length -= blk_sz) { + if ((uintmax_t)blk_sz > (uintmax_t)length) + blk_sz = length; + p1 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED, + fd1, skip1); + if (p1 == MAP_FAILED) + goto mmap_failed; + + p2 = mmap(NULL, blk_sz, PROT_READ, MAP_FILE|MAP_SHARED, + fd2, skip2); + if (p2 == MAP_FAILED) { + munmap(p1, blk_sz); + goto mmap_failed; + } + + blk_cnt = blk_sz; + for (; blk_cnt--; ++p1, ++p2, ++byte) { + if ((ch = *p1) != *p2) { + if (!lflag) { + diffmsg(file1, file2, byte, line); + /* NOTREACHED */ + } + dfound = 1; + (void)printf("%6lld %3o %3o\n", + (long long)byte, ch, *p2); + } + if (ch == '\n') + ++line; + } + munmap(p1 - blk_sz, blk_sz); + munmap(p2 - blk_sz, blk_sz); + skip1 += blk_sz; + skip2 += blk_sz; + } + + if (len1 != len2) + eofmsg(len1 > len2 ? file2 : file1, byte, line); + if (dfound) + exit(DIFF_EXIT); + return; + +mmap_failed: + c_special(fd1, file1, skip1, fd2, file2, skip2); +} diff --git a/usr.bin/cmp/special.c b/usr.bin/cmp/special.c new file mode 100644 index 0000000..0b1afc5 --- /dev/null +++ b/usr.bin/cmp/special.c @@ -0,0 +1,111 @@ +/* $NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)special.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: special.c,v 1.14 2011/11/28 10:10:10 wiz Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include + +#include "extern.h" + +void +c_special(int fd1, const char *file1, off_t skip1, int fd2, const char *file2, off_t skip2) +{ + int ch1, ch2; + off_t byte, line; + FILE *fp1, *fp2; + int dfound; + + dfound = 0; + if ((fp1 = fdopen(fd1, "r")) == NULL) + err(ERR_EXIT, "%s", file1); + if ((fp2 = fdopen(fd2, "r")) == NULL) + err(ERR_EXIT, "%s", file2); + + for (byte = line = 1; skip1--; byte++) { + ch1 = getc(fp1); + if (ch1 == EOF) + goto eof; + if (ch1 == '\n') + line++; + } + for (byte = line = 1; skip2--; byte++) { + ch2 = getc(fp2); + if (ch2 == EOF) + goto eof; + if (ch2 == '\n') + line++; + } + dfound = 0; + for (byte = line = 1;; ++byte) { + ch1 = getc(fp1); + ch2 = getc(fp2); + if (ch1 == EOF || ch2 == EOF) + break; + if (ch1 != ch2) { + if (lflag) { + dfound = 1; + (void)printf("%6lld %3o %3o\n", (long long)byte, + ch1, ch2); + } else + diffmsg(file1, file2, byte, line); + /* NOTREACHED */ + } + if (ch1 == '\n') + ++line; + } + + eof: + if (ferror(fp1)) + errmsg(file1, byte, line); + if (ferror(fp2)) + errmsg(file2, byte, line); + if (feof(fp1)) { + if (!feof(fp2)) + eofmsg(file1, byte, line); + } else + if (feof(fp2)) + eofmsg(file2, byte, line); + (void)fclose(fp1); + (void)fclose(fp2); + if (dfound) + exit(DIFF_EXIT); +} diff --git a/usr.bin/comm/comm.1 b/usr.bin/comm/comm.1 new file mode 100644 index 0000000..d304388 --- /dev/null +++ b/usr.bin/comm/comm.1 @@ -0,0 +1,102 @@ +.\" $NetBSD: comm.1,v 1.12 2014/04/13 01:45:34 snj Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)comm.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt COMM 1 +.Os +.Sh NAME +.Nm comm +.Nd select or reject lines common to two files +.Sh SYNOPSIS +.Nm +.Op Fl 123f +.Ar file1 file2 +.Sh DESCRIPTION +The +.Nm +utility reads +.Ar file1 +and +.Ar file2 , +which should be +sorted lexically, and produces three text +columns as output: lines only in +.Ar file1 ; +lines only in +.Ar file2 ; +and lines in both files. +.Pp +The filename ``-'' means the standard input. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl 1 +Suppress printing of column 1. +.It Fl 2 +Suppress printing of column 2. +.It Fl 3 +Suppress printing of column 3. +.It Fl f +Fold case in line comparisons. +.El +.Pp +Each column will have a number of tab characters prepended to it +equal to the number of lower numbered columns that are being printed. +For example, if column number two is being suppressed, lines printed +in column number one will not have any tabs preceding them, and lines +printed in column number three will have one. +.Pp +.Nm +assumes that the files are lexically sorted; all characters +participate in line comparisons. +.\" .Sh ENVIRONMENT +.\" .Bl -tag -width indent +.\" .It Ev LANG +.\" .It Ev LC_ALL +.\" .It Ev LC_CTYPE +.\" .It Ev LC_COLLATE +.\" .It Ev LC_MESSAGES +.\" .El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr cmp 1 , +.Xr diff 1 , +.Xr sort 1 , +.Xr uniq 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/comm/comm.c b/usr.bin/comm/comm.c new file mode 100644 index 0000000..c1416e2 --- /dev/null +++ b/usr.bin/comm/comm.c @@ -0,0 +1,213 @@ +/* $NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Case Larsen. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)comm.c 8.4 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: comm.c,v 1.20 2012/09/05 04:01:23 simonb Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +#define MAXLINELEN (LINE_MAX + 1) + +static const char *tabs[] = { "", "\t", "\t\t" }; + +static FILE *file(const char *); +static void show(FILE *, const char *, char *); +__dead static void usage(void); +static char *getnextln(char *buf, FILE *); + +int +main(int argc, char **argv) +{ + int comp, file1done, file2done, read1, read2; + int ch, flag1, flag2, flag3; + FILE *fp1, *fp2; + const char *col1, *col2, *col3, **p; + char line1[MAXLINELEN], line2[MAXLINELEN]; + int (*compare)(const char*,const char*); + + (void)setlocale(LC_ALL, ""); + + file1done = file2done = 0; + flag1 = flag2 = flag3 = 1; + compare = strcoll; + while ((ch = getopt(argc, argv, "123f")) != -1) + switch(ch) { + case '1': + flag1 = 0; + break; + case '2': + flag2 = 0; + break; + case '3': + flag3 = 0; + break; + case 'f': + compare = strcasecmp; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc != 2) + usage(); + + fp1 = file(argv[0]); + fp2 = file(argv[1]); + + /* for each column printed, add another tab offset */ + p = tabs; + col1 = col2 = col3 = NULL; + if (flag1) + col1 = *p++; + if (flag2) + col2 = *p++; + if (flag3) + col3 = *p; + + for (read1 = read2 = 1;;) { + /* read next line, check for EOF */ + if (read1) + file1done = !getnextln(line1, fp1); + if (read2) + file2done = !getnextln(line2, fp2); + + /* if one file done, display the rest of the other file */ + if (file1done) { + if (!file2done && col2) + show(fp2, col2, line2); + break; + } + if (file2done) { + if (!file1done && col1) + show(fp1, col1, line1); + break; + } + + /* lines are the same */ + if (!(comp = compare(line1, line2))) { + read1 = read2 = 1; + if (col3) + if (printf("%s%s\n", col3, line1) < 0) + break; + continue; + } + + /* lines are different */ + if (comp < 0) { + read1 = 1; + read2 = 0; + if (col1) + if (printf("%s%s\n", col1, line1) < 0) + break; + } else { + read1 = 0; + read2 = 1; + if (col2) + if (printf("%s%s\n", col2, line2) < 0) + break; + } + } + + if (ferror (stdout) || fclose (stdout) == EOF) + err(1, "stdout"); + + exit(0); +} + +static void +show(FILE *fp, const char *offset, char *buf) +{ + while (printf("%s%s\n", offset, buf) >= 0 && getnextln(buf, fp)) + ; +} + +static FILE * +file(const char *name) +{ + FILE *fp; + + if (!strcmp(name, "-")) + return (stdin); + if ((fp = fopen(name, "r")) == NULL) + err(1, "%s", name); + return (fp); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: comm [-123f] file1 file2\n"); + exit(1); +} + +static char * +getnextln(char *buf, FILE *fp) +{ + size_t i = 0; + int c; + + while ((c = getc(fp)) != '\n' && c != EOF) { + buf[i++] = c; + + if (i >= MAXLINELEN) + i--; /* consumes extra characters till newline */ + } + + if (c == EOF && !i) + return NULL; + + buf[i] = 0; + return buf; +} + diff --git a/usr.bin/compress/compress.1 b/usr.bin/compress/compress.1 new file mode 100644 index 0000000..84bc47c --- /dev/null +++ b/usr.bin/compress/compress.1 @@ -0,0 +1,171 @@ +.\" $NetBSD: compress.1,v 1.16 2017/07/04 06:58:55 wiz Exp $ +.\" +.\" Copyright (c) 1986, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" James A. Woods, derived from original work by Spencer Thomas +.\" and Joseph Orost. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)compress.1 8.2 (Berkeley) 4/18/94 +.\" +.Dd January 23, 2003 +.Dt COMPRESS 1 +.Os +.Sh NAME +.Nm compress , +.\".Nm uncompress , +.Nm uncompress +.\".Nm zcat +.Nd compress and expand data +.Sh SYNOPSIS +.Nm +.Op Fl cdfv +.Op Fl b Ar bits +.Op Ar +.Nm uncompress +.Op Fl cdfv +.Op Ar +.\".Nm zcat +.\".Op Ar +.Sh DESCRIPTION +.Nm +reduces the size of the named files using adaptive Lempel-Ziv coding. +Each +.Ar file +is renamed to the same name plus the extension +.Dq .Z . +As many of the modification time, access time, file flags, file mode, +user ID, and group ID as allowed by permissions are retained in the +new file. +If compression would not reduce the size of a +.Ar file , +the file is ignored. +.Pp +.Nm uncompress +restores the compressed files to their original form, renaming the +files by deleting the +.Dq .Z +extension. +.\".Pp +.\".Nm Zcat +.\"is an alias for +.\".Dq "uncompress -c" . +.Pp +If renaming the files would cause files to be overwritten and the standard +input device is a terminal, the user is prompted (on the standard error +output) for confirmation. +If prompting is not possible or confirmation is not received, the files +are not overwritten. +.Pp +If no files are specified, the standard input is compressed or uncompressed +to the standard output. +If either the input and output files are not regular files, the checks for +reduction in size and file overwriting are not performed, the input file is +not removed, and the attributes of the input file are not retained. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl b +Specify the +.Ar bits +code limit (see below). +.It Fl c +Compressed or uncompressed output is written to the standard output. +No files are modified. +.It Fl d +Force decompression. +.It Fl f +Force compression of +.Ar file , +even if it is not actually reduced in size. +Additionally, files are overwritten without prompting for confirmation. +.It Fl v +Print the percentage reduction of each file. +.El +.Pp +.Nm +uses a modified Lempel-Ziv algorithm. +Common substrings in the file are first replaced by 9-bit codes 257 and up. +When code 512 is reached, the algorithm switches to 10-bit codes and +continues to use more bits until the +limit specified by the +.Fl b +flag is reached (the default is 16). +.Ar Bits +must be between 9 and 16. +.Pp +After the +.Ar bits +limit is reached, +.Nm +periodically checks the compression ratio. +If it is increasing, +.Nm +continues to use the existing code dictionary. +However, if the compression ratio decreases, +.Nm +discards the table of substrings and rebuilds it from scratch. +This allows the algorithm to adapt to the next "block" of the file. +.Pp +The +.Fl b +flag is omitted for +.Ar uncompress +since the +.Ar bits +parameter specified during compression +is encoded within the output, along with +a magic number to ensure that neither decompression of random data nor +recompression of compressed data is attempted. +.Pp +The amount of compression obtained depends on the size of the +input, the number of +.Ar bits +per code, and the distribution of common substrings. +Typically, text such as source code or English is reduced by 50\-60%. +Compression is generally much better than that achieved by Huffman +coding (as used in the historical command pack), or adaptive Huffman +coding (as used in the historical command compact), and takes less +time to compute. +.Sh EXIT STATUS +.Ex -std compress +.Sh SEE ALSO +.Xr zcat 1 +.Rs +.%A Welch, Terry A. +.%D June, 1984 +.%T "A Technique for High Performance Data Compression" +.%J "IEEE Computer" +.%V 17:6 +.%P pp. 8-19 +.Re +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 . diff --git a/usr.bin/compress/compress.c b/usr.bin/compress/compress.c new file mode 100644 index 0000000..93fdc76 --- /dev/null +++ b/usr.bin/compress/compress.c @@ -0,0 +1,459 @@ +/* $NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94"; +#else +__RCSID("$NetBSD: compress.c,v 1.26 2011/08/30 23:08:05 joerg Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static void compress(const char *, const char *, int); +static void cwarn(const char *, ...) __printflike(1, 2); +static void cwarnx(const char *, ...) __printflike(1, 2); +static void decompress(const char *, const char *, int); +static int permission(const char *); +static void setfile(const char *, struct stat *); +__dead static void usage(int); + +extern FILE *zopen(const char *fname, const char *mode, int bits); + +static int eval, force, verbose; +static int isstdout, isstdin; + +int +main(int argc, char **argv) +{ + enum {COMPRESS, DECOMPRESS} style = COMPRESS; + size_t len; + int bits, cat, ch; + char *p, newname[MAXPATHLEN]; + + if ((p = strrchr(argv[0], '/')) == NULL) + p = argv[0]; + else + ++p; + if (!strcmp(p, "uncompress")) + style = DECOMPRESS; + else if (!strcmp(p, "compress")) + style = COMPRESS; + else if (!strcmp(p, "zcat")) { + style = DECOMPRESS; + cat = 1; + } + else + errx(1, "unknown program name"); + + bits = cat = 0; + while ((ch = getopt(argc, argv, "b:cdfv")) != -1) + switch(ch) { + case 'b': + bits = strtol(optarg, &p, 10); + if (*p) + errx(1, "illegal bit count -- %s", optarg); + break; + case 'c': + cat = 1; + break; + case 'd': /* Backward compatible. */ + style = DECOMPRESS; + break; + case 'f': + force = 1; + break; + case 'v': + verbose = 1; + break; + case '?': + default: + usage(style == COMPRESS); + } + argc -= optind; + argv += optind; + + if (argc == 0) { + switch(style) { + case COMPRESS: + isstdout = 1; + isstdin = 1; + (void)compress("/dev/stdin", "/dev/stdout", bits); + break; + case DECOMPRESS: + isstdout = 1; + isstdin = 1; + (void)decompress("/dev/stdin", "/dev/stdout", bits); + break; + } + exit (eval); + } + + if (cat == 1 && argc > 1) + errx(1, "the -c option permits only a single file argument"); + + for (; *argv; ++argv) { + isstdout = 0; + switch(style) { + case COMPRESS: + if (cat) { + isstdout = 1; + compress(*argv, "/dev/stdout", bits); + break; + } + if ((p = strrchr(*argv, '.')) != NULL && + !strcmp(p, ".Z")) { + cwarnx("%s: name already has trailing .Z", + *argv); + break; + } + len = strlen(*argv); + if (len > sizeof(newname) - 3) { + cwarnx("%s: name too long", *argv); + break; + } + memmove(newname, *argv, len); + newname[len] = '.'; + newname[len + 1] = 'Z'; + newname[len + 2] = '\0'; + compress(*argv, newname, bits); + break; + case DECOMPRESS: + len = strlen(*argv); + if ((p = strrchr(*argv, '.')) == NULL || + strcmp(p, ".Z")) { + if (len > sizeof(newname) - 3) { + cwarnx("%s: name too long", *argv); + break; + } + memmove(newname, *argv, len); + newname[len] = '.'; + newname[len + 1] = 'Z'; + newname[len + 2] = '\0'; + decompress(newname, + cat ? "/dev/stdout" : *argv, bits); + if (cat) + isstdout = 1; + } else { + if (len - 2 > sizeof(newname) - 1) { + cwarnx("%s: name too long", *argv); + break; + } + memmove(newname, *argv, len - 2); + newname[len - 2] = '\0'; + decompress(*argv, + cat ? "/dev/stdout" : newname, bits); + if (cat) + isstdout = 1; + } + break; + } + } + exit (eval); +} + +static void +compress(const char *in, const char *out, int bits) +{ + size_t nr; + struct stat isb, sb; + const char *error = NULL; + FILE *ifp, *ofp; + int exists, isreg, oreg; + u_char buf[BUFSIZ]; + + if (!isstdout) { + exists = !stat(out, &sb); + if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) + return; + oreg = !exists || S_ISREG(sb.st_mode); + } else + oreg = 0; + + ifp = ofp = NULL; + if ((ifp = fopen(in, "r")) == NULL) { + cwarn("%s", in); + return; + } + + if (!isstdin) { + if (stat(in, &isb)) { /* DON'T FSTAT! */ + cwarn("%s", in); + goto err; + } + if (!S_ISREG(isb.st_mode)) + isreg = 0; + else + isreg = 1; + } else + isreg = 0; + + if ((ofp = zopen(out, "w", bits)) == NULL) { + cwarn("%s", out); + goto err; + } + oreg <<= 1; + while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) + if (fwrite(buf, 1, nr, ofp) != nr) { + cwarn("%s", out); + goto err; + } + + if (ferror(ifp)) + error = in; + if (fclose(ifp)) + if (error == NULL) + error = in; + if (fclose(ofp)) + if (error == NULL) + error = out; + ifp = NULL; + ofp = NULL; + if (error) { + cwarn("%s", error); + goto err; + } + + if (isreg && oreg) { + if (stat(out, &sb)) { + cwarn("%s", out); + goto err; + } + + if (!force && sb.st_size >= isb.st_size) { + if (verbose) + (void)printf("%s: file would grow; left unmodified\n", in); + goto err; + } + + setfile(out, &isb); + + if (unlink(in)) + cwarn("%s", in); + + if (verbose) { + (void)printf("%s: ", out); + if (isb.st_size > sb.st_size) + (void)printf("%.0f%% compression\n", + ((double)sb.st_size / isb.st_size) * 100.0); + else + (void)printf("%.0f%% expansion\n", + ((double)isb.st_size / sb.st_size) * 100.0); + } + } + return; + +err: if (ofp) + (void)fclose(ofp); + if (oreg == 2) + (void)unlink(out); + if (ifp) + (void)fclose(ifp); +} + +static void +decompress(const char *in, const char *out, int bits) +{ + size_t nr; + struct stat sb; + FILE *ifp, *ofp; + int exists, isreg, oreg; + u_char buf[BUFSIZ]; + + if (!isstdout) { + exists = !stat(out, &sb); + if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) + return; + oreg = !exists || S_ISREG(sb.st_mode); + } else + oreg = 0; + + ifp = ofp = NULL; + if ((ofp = fopen(out, "w")) == NULL) { + cwarn("%s", out); + return; + } + + if ((ifp = zopen(in, "r", bits)) == NULL) { + cwarn("%s", in); + goto err; + } + if (!isstdin) { + if (stat(in, &sb)) { + cwarn("%s", in); + goto err; + } + if (!S_ISREG(sb.st_mode)) + isreg = 0; + else + isreg = 1; + } else + isreg = 0; + + oreg <<= 1; + while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) + if (fwrite(buf, 1, nr, ofp) != nr) { + cwarn("%s", out); + goto err; + } + + if (ferror(ifp)) { + cwarn("%s", in); + goto err; + } + if (fclose(ifp)) { + ifp = NULL; + cwarn("%s", in); + goto err; + } + ifp = NULL; + + if (fclose(ofp)) { + ofp = NULL; + cwarn("%s", out); + goto err; + } + + if (isreg && oreg) { + setfile(out, &sb); + + if (unlink(in)) + cwarn("%s", in); + } + return; + +err: if (ofp) + (void)fclose(ofp); + if (oreg == 2) + (void)unlink(out); + if (ifp) + (void)fclose(ifp); +} + +static void +setfile(const char *name, struct stat *fs) +{ + static struct timeval tv[2]; + + fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; + + TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); + TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); + if (utimes(name, tv)) + cwarn("utimes: %s", name); + + /* + * Changing the ownership probably won't succeed, unless we're root + * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting + * the mode; current BSD behavior is to remove all setuid bits on + * chown. If chown fails, lose setuid/setgid bits. + */ + if (chown(name, fs->st_uid, fs->st_gid)) { + if (errno != EPERM) + cwarn("chown: %s", name); + fs->st_mode &= ~(S_ISUID|S_ISGID); + } + if (chmod(name, fs->st_mode)) + cwarn("chown: %s", name); + + /* + * Restore the file's flags. However, do this only if the original + * file had any flags set; this avoids a warning on file-systems that + * do not support flags. + */ + if (fs->st_flags != 0 && chflags(name, fs->st_flags)) + cwarn("chflags: %s", name); +} + +static int +permission(const char *fname) +{ + int ch, first; + + if (!isatty(fileno(stderr))) + return (0); + (void)fprintf(stderr, "overwrite %s? ", fname); + first = ch = getchar(); + while (ch != '\n' && ch != EOF) + ch = getchar(); + return (first == 'y'); +} + +static void +usage(int iscompress) +{ + if (iscompress) + (void)fprintf(stderr, + "usage: compress [-cdfv] [-b bits] [file ...]\n"); + else + (void)fprintf(stderr, + "usage: uncompress [-cdfv] [-b bits] [file ...]\n"); + exit(1); +} + +static void +cwarnx(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarnx(fmt, ap); + va_end(ap); + eval = 1; +} + +static void +cwarn(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); + eval = 1; +} diff --git a/usr.bin/compress/doc/NOTES b/usr.bin/compress/doc/NOTES new file mode 100644 index 0000000..26181bf --- /dev/null +++ b/usr.bin/compress/doc/NOTES @@ -0,0 +1,139 @@ +From: James A. Woods + +>From vn Fri Dec 2 18:05:27 1988 +Subject: Re: Looking for C source for RSA +Newsgroups: sci.crypt + +# Illegitimi noncarborundum + +Patents are a tar pit. + +A good case can be made that most are just a license to sue, and nothing +is illegal until a patent is upheld in court. + +For example, if you receive netnews by means other than 'nntp', +these very words are being modulated by 'compress', +a variation on the patented Lempel-Ziv-Welch algorithm. + +Original Ziv-Lempel is patent number 4,464,650, and the more powerful +LZW method is #4,558,302. Yet despite any similarities between 'compress' +and LZW (the public-domain 'compress' code was designed and given to the +world before the ink on the Welch patent was dry), no attorneys from Sperry +(the assignee) have asked you to unplug your Usenet connection. + +Why? I can't speak for them, but it is possible the claims are too broad, +or, just as bad, not broad enough. ('compress' does things not mentioned +in the Welch patent.) Maybe they realize that they can commercialize +LZW better by selling hardware implementations rather than by licensing +software. Again, the LZW software delineated in the patent is *not* +the same as that of 'compress'. + +At any rate, court-tested software patents are a different animal; +corporate patents in a portfolio are usually traded like baseball cards +to shut out small fry rather than actually be defended before +non-technical juries. Perhaps RSA will undergo this test successfully, +although the grant to "exclude others from making, using, or selling" +the invention would then only apply to the U.S. (witness the +Genentech patent of the TPA molecule in the U.S. but struck down +in Great Britain as too broad.) + +The concept is still exotic for those who learned in school the rule of thumb +that one may patent "apparatus" but not an "idea". +Apparently this all changed in Diamond v. Diehr (1981) when the U. S. Supreme +Court reversed itself. + +Scholars should consult the excellent article in the Washington and Lee +Law Review (fall 1984, vol. 41, no. 4) by Anthony and Colwell for a +comprehensive survey of an area which will remain murky for some time. + +Until the dust clears, how you approach ideas which are patented depends +on how paranoid you are of a legal onslaught. Arbitrary? Yes. But +the patent bar the CCPA (Court of Customs and Patent Appeals) +thanks you for any uncertainty as they, at least, stand to gain +from any trouble. + +=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= +From: James A. Woods +Subject: Re: Looking for C source for RSA (actually 'compress' patents) + + In article <2042@eos.UUCP> you write: + >The concept is still exotic for those who learned in school the rule of thumb + >that one may patent "apparatus" but not an "idea". + +A rule of thumb that has never been completely valid, as any chemical +engineer can tell you. (Chemical processes were among the earliest patents, +as I recall.) + + ah yes -- i date myself when relaying out-of-date advice from elderly + attorneys who don't even specialize in patents. one other interesting + class of patents include the output of optical lens design programs, + which yield formulae which can then fairly directly can be molded + into glass. although there are restrictions on patenting equations, + the "embedded systems" seem to fly past the legal gauntlets. + + anyway, i'm still learning about intellectual property law after + several conversations from a unisys (nee sperry) lawyer re 'compress'. + + it's more complicated than this, but they're letting (oral + communication only) software versions of 'compress' slide + as far as licensing fees go. this includes 'arc', 'stuffit', + and other commercial wrappers for 'compress'. yet they are + signing up licensees for hardware chips. hewlett-packard + supposedly has an active vlsi project, and unisys has + board-level lzw-based tape controllers. (to build lzw into + a disk controller would be strange, as you'd have to build + in a filesystem too!) + + it's byzantine + that unisys is in a tiff with hp regarding the patents, + after discovering some sort of "compress" button on some + hp terminal product. why? well, professor abraham lempel jumped + from being department chairman of computer science at technion in + israel to sperry (where he got the first patent), but then to work + at hewlett-packard on sabbatical. the second welch patent + is only weakly derivative of the first, so they want chip + licenses and hp relented. however, everyone agrees something + like the current unix implementation is the way to go with + software, so hp (and ucb) long ago asked spencer thomas and i to sign + off on copyright permission (although they didn't need to, it being pd). + lempel, hp, and unisys grumbles they can't make money off the + software since a good free implementation (not the best -- + i have more ideas!) escaped via usenet. (lempel's own pascal + code was apparently horribly slow.) + i don't follow the ibm 'arc' legal bickering; my impression + is that the pc folks are making money off the archiver/wrapper + look/feel of the thing [if ms-dos can be said to have a look and feel]. + + now where is telebit with the compress firmware? in a limbo + netherworld, probably, with sperry still welcoming outfits + to sign patent licenses, a common tactic to bring other small fry + into the fold. the guy who crammed 12-bit compess into the modem + there left. also what is transpiring with 'compress' and sys 5 rel 4? + beats me, but if sperry got a hold of them on these issues, + at&t would likely re-implement another algorithm if they + thought 'compress' infringes. needful to say, i don't think + it does after the abovementioned legal conversation. + my own beliefs on whether algorithms should be patentable at all + change with the weather. if the courts finally nail down + patent protection for algorithms, academic publication in + textbooks will be somewhat at odds with the engineering world, + where the textbook codes will simply be a big tease to get + money into the patent holder coffers... + + oh, if you implement lzw from the patent, you won't get + good rates because it doesn't mention adaptive table reset, + lack thereof being *the* serious deficiency of thomas' first version. + + now i know that patent law generally protects against independent + re-invention (like the 'xor' hash function pleasantly mentioned + in the patent [but not the paper]). + but the upshot is that if anyone ever wanted to sue us, + we're partially covered with + independently-developed twists, plus the fact that some of us work + in a bureacratic morass (as contractor to a public agency in my case). + + quite a mess, huh? i've wanted to tell someone this stuff + for a long time, for posterity if nothing else. + +james + diff --git a/usr.bin/compress/doc/README b/usr.bin/compress/doc/README new file mode 100644 index 0000000..6803287 --- /dev/null +++ b/usr.bin/compress/doc/README @@ -0,0 +1,283 @@ + + @(#)README 8.1 (Berkeley) 6/9/93 + +Compress version 4.0 improvements over 3.0: + o compress() speedup (10-50%) by changing division hash to xor + o decompress() speedup (5-10%) + o Memory requirements reduced (3-30%) + o Stack requirements reduced to less than 4kb + o Removed 'Big+Fast' compress code (FBITS) because of compress speedup + o Portability mods for Z8000 and PC/XT (but not zeus 3.2) + o Default to 'quiet' mode + o Unification of 'force' flags + o Manual page overhaul + o Portability enhancement for M_XENIX + o Removed text on #else and #endif + o Added "-V" switch to print version and options + o Added #defines for SIGNED_COMPARE_SLOW + o Added Makefile and "usermem" program + o Removed all floating point computations + o New programs: [deleted] + +The "usermem" script attempts to determine the maximum process size. Some +editing of the script may be necessary (see the comments). [It should work +fine on 4.3 bsd.] If you can't get it to work at all, just create file +"USERMEM" containing the maximum process size in decimal. + +The following preprocessor symbols control the compilation of "compress.c": + + o USERMEM Maximum process memory on the system + o SACREDMEM Amount to reserve for other proceses + o SIGNED_COMPARE_SLOW Unsigned compare instructions are faster + o NO_UCHAR Don't use "unsigned char" types + o BITS Overrules default set by USERMEM-SACREDMEM + o vax Generate inline assembler + o interdata Defines SIGNED_COMPARE_SLOW + o M_XENIX Makes arrays < 65536 bytes each + o pdp11 BITS=12, NO_UCHAR + o z8000 BITS=12 + o pcxt BITS=12 + o BSD4_2 Allow long filenames ( > 14 characters) & + Call setlinebuf(stderr) + +The difference "usermem-sacredmem" determines the maximum BITS that can be +specified with the "-b" flag. + +memory: at least BITS +------ -- ----- ---- + 433,484 16 + 229,600 15 + 127,536 14 + 73,464 13 + 0 12 + +The default is BITS=16. + +The maximum bits can be overrulled by specifying "-DBITS=bits" at +compilation time. + +WARNING: files compressed on a large machine with more bits than allowed by +a version of compress on a smaller machine cannot be decompressed! Use the +"-b12" flag to generate a file on a large machine that can be uncompressed +on a 16-bit machine. + +The output of compress 4.0 is fully compatible with that of compress 3.0. +In other words, the output of compress 4.0 may be fed into uncompress 3.0 or +the output of compress 3.0 may be fed into uncompress 4.0. + +The output of compress 4.0 not compatible with that of +compress 2.0. However, compress 4.0 still accepts the output of +compress 2.0. To generate output that is compatible with compress +2.0, use the undocumented "-C" flag. + + -from mod.sources, submitted by vax135!petsd!joe (Joe Orost), 8/1/85 +-------------------------------- + +Enclosed is compress version 3.0 with the following changes: + +1. "Block" compression is performed. After the BITS run out, the + compression ratio is checked every so often. If it is decreasing, + the table is cleared and a new set of substrings are generated. + + This makes the output of compress 3.0 not compatible with that of + compress 2.0. However, compress 3.0 still accepts the output of + compress 2.0. To generate output that is compatible with compress + 2.0, use the undocumented "-C" flag. + +2. A quiet "-q" flag has been added for use by the news system. + +3. The character chaining has been deleted and the program now uses + hashing. This improves the speed of the program, especially + during decompression. Other speed improvements have been made, + such as using putc() instead of fwrite(). + +4. A large table is used on large machines when a relatively small + number of bits is specified. This saves much time when compressing + for a 16-bit machine on a 32-bit virtual machine. Note that the + speed improvement only occurs when the input file is > 30000 + characters, and the -b BITS is less than or equal to the cutoff + described below. + +Most of these changes were made by James A. Woods (ames!jaw). Thank you +James! + +To compile compress: + + cc -O -DUSERMEM=usermem -o compress compress.c + +Where "usermem" is the amount of physical user memory available (in bytes). +If any physical memory is to be reserved for other processes, put in +"-DSACREDMEM sacredmem", where "sacredmem" is the amount to be reserved. + +The difference "usermem-sacredmem" determines the maximum BITS that can be +specified, and the cutoff bits where the large+fast table is used. + +memory: at least BITS cutoff +------ -- ----- ---- ------ + 4,718,592 16 13 + 2,621,440 16 12 + 1,572,864 16 11 + 1,048,576 16 10 + 631,808 16 -- + 329,728 15 -- + 178,176 14 -- + 99,328 13 -- + 0 12 -- + +The default memory size is 750,000 which gives a maximum BITS=16 and no +large+fast table. + +The maximum bits can be overruled by specifying "-DBITS=bits" at +compilation time. + +If your machine doesn't support unsigned characters, define "NO_UCHAR" +when compiling. + +If your machine has "int" as 16-bits, define "SHORT_INT" when compiling. + +After compilation, move "compress" to a standard executable location, such +as /usr/local. Then: + cd /usr/local + ln compress uncompress + ln compress zcat + +On machines that have a fixed stack size (such as Perkin-Elmer), set the +stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer). + +Next, install the manual (compress.l). + cp compress.l /usr/man/manl + cd /usr/man/manl + ln compress.l uncompress.l + ln compress.l zcat.l + + - or - + + cp compress.l /usr/man/man1/compress.1 + cd /usr/man/man1 + ln compress.1 uncompress.1 + ln compress.1 zcat.1 + + regards, + petsd!joe + +Here is a note from the net: + +>From hplabs!pesnta!amd!turtlevax!ken Sat Jan 5 03:35:20 1985 +Path: ames!hplabs!pesnta!amd!turtlevax!ken +From: ken@turtlevax.UUCP (Ken Turkowski) +Newsgroups: net.sources +Subject: Re: Compress release 3.0 : sample Makefile +Organization: CADLINC, Inc. @ Menlo Park, CA + +In the compress 3.0 source recently posted to mod.sources, there is a +#define variable which can be set for optimum performance on a machine +with a large amount of memory. A program (usermem) to calculate the +useable amount of physical user memory is enclosed, as well as a sample +4.2bsd Vax Makefile for compress. + +Here is the README file from the previous version of compress (2.0): + +>Enclosed is compress.c version 2.0 with the following bugs fixed: +> +>1. The packed files produced by compress are different on different +> machines and dependent on the vax sysgen option. +> The bug was in the different byte/bit ordering on the +> various machines. This has been fixed. +> +> This version is NOT compatible with the original vax posting +> unless the '-DCOMPATIBLE' option is specified to the C +> compiler. The original posting has a bug which I fixed, +> causing incompatible files. I recommend you NOT to use this +> option unless you already have a lot of packed files from +> the original posting by thomas. +>2. The exit status is not well defined (on some machines) causing the +> scripts to fail. +> The exit status is now 0,1 or 2 and is documented in +> compress.l. +>3. The function getopt() is not available in all C libraries. +> The function getopt() is no longer referenced by the +> program. +>4. Error status is not being checked on the fwrite() and fflush() calls. +> Fixed. +> +>The following enhancements have been made: +> +>1. Added facilities of "compact" into the compress program. "Pack", +> "Unpack", and "Pcat" are no longer required (no longer supplied). +>2. Installed work around for C compiler bug with "-O". +>3. Added a magic number header (\037\235). Put the bits specified +> in the file. +>4. Added "-f" flag to force overwrite of output file. +>5. Added "-c" flag and "zcat" program. 'ln compress zcat' after you +> compile. +>6. The 'uncompress' script has been deleted; simply +> 'ln compress uncompress' after you compile and it will work. +>7. Removed extra bit masking for machines that support unsigned +> characters. If your machine doesn't support unsigned characters, +> define "NO_UCHAR" when compiling. +> +>Compile "compress.c" with "-O -o compress" flags. Move "compress" to a +>standard executable location, such as /usr/local. Then: +> cd /usr/local +> ln compress uncompress +> ln compress zcat +> +>On machines that have a fixed stack size (such as Perkin-Elmer), set the +>stack to at least 12kb. ("setstack compress 12" on Perkin-Elmer). +> +>Next, install the manual (compress.l). +> cp compress.l /usr/man/manl - or - +> cp compress.l /usr/man/man1/compress.1 +> +>Here is the README that I sent with my first posting: +> +>>Enclosed is a modified version of compress.c, along with scripts to make it +>>run identically to pack(1), unpack(1), an pcat(1). Here is what I +>>(petsd!joe) and a colleague (petsd!peora!srd) did: +>> +>>1. Removed VAX dependencies. +>>2. Changed the struct to separate arrays; saves mucho memory. +>>3. Did comparisons in unsigned, where possible. (Faster on Perkin-Elmer.) +>>4. Sorted the character next chain and changed the search to stop +>>prematurely. This saves a lot on the execution time when compressing. +>> +>>This version is totally compatible with the original version. Even though +>>lint(1) -p has no complaints about compress.c, it won't run on a 16-bit +>>machine, due to the size of the arrays. +>> +>>Here is the README file from the original author: +>> +>>>Well, with all this discussion about file compression (for news batching +>>>in particular) going around, I decided to implement the text compression +>>>algorithm described in the June Computer magazine. The author claimed +>>>blinding speed and good compression ratios. It's certainly faster than +>>>compact (but, then, what wouldn't be), but it's also the same speed as +>>>pack, and gets better compression than both of them. On 350K bytes of +>>>unix-wizards, compact took about 8 minutes of CPU, pack took about 80 +>>>seconds, and compress (herein) also took 80 seconds. But, compact and +>>>pack got about 30% compression, whereas compress got over 50%. So, I +>>>decided I had something, and that others might be interested, too. +>>> +>>>As is probably true of compact and pack (although I haven't checked), +>>>the byte order within a word is probably relevant here, but as long as +>>>you stay on a single machine type, you should be ok. (Can anybody +>>>elucidate on this?) There are a couple of asm's in the code (extv and +>>>insv instructions), so anyone porting it to another machine will have to +>>>deal with this anyway (and could probably make it compatible with Vax +>>>byte order at the same time). Anyway, I've linted the code (both with +>>>and without -p), so it should run elsewhere. Note the longs in the +>>>code, you can take these out if you reduce BITS to <= 15. +>>> +>>>Have fun, and as always, if you make good enhancements, or bug fixes, +>>>I'd like to see them. +>>> +>>>=Spencer (thomas@utah-20, {harpo,hplabs,arizona}!utah-cs!thomas) +>> +>> regards, +>> joe +>> +>>-- +>>Full-Name: Joseph M. Orost +>>UUCP: ..!{decvax,ucbvax,ihnp4}!vax135!petsd!joe +>>US Mail: MS 313; Perkin-Elmer; 106 Apple St; Tinton Falls, NJ 07724 +>>Phone: (201) 870-5844 diff --git a/usr.bin/compress/doc/revision.log b/usr.bin/compress/doc/revision.log new file mode 100644 index 0000000..16ca49d --- /dev/null +++ b/usr.bin/compress/doc/revision.log @@ -0,0 +1,116 @@ +/* + * $Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $ + * $Lg: compress.c,v $ + * Revision 4.0 85/07/30 12:50:00 joe + * Removed ferror() calls in output routine on every output except first. + * Prepared for release to the world. + * + * Revision 3.6 85/07/04 01:22:21 joe + * Remove much wasted storage by overlaying hash table with the tables + * used by decompress: tab_suffix[1<putc] and + * added signal catcher [plus beef in writeerr()] to delete effluvia. + * + * Revision 2.0 84/08/28 22:00:00 petsd!joe + * Add check for foreground before prompting user. Insert maxbits into + * compressed file. Force file being uncompressed to end with ".Z". + * Added "-c" flag and "zcat". Prepared for release. + * + * Revision 1.10 84/08/24 18:28:00 turtlevax!ken + * Will only compress regular files (no directories), added a magic number + * header (plus an undocumented -n flag to handle old files without headers), + * added -f flag to force overwriting of possibly existing destination file, + * otherwise the user is prompted for a response. Will tack on a .Z to a + * filename if it doesn't have one when decompressing. Will only replace + * file if it was compressed. + * + * Revision 1.9 84/08/16 17:28:00 turtlevax!ken + * Removed scanargs(), getopt(), added .Z extension and unlimited number of + * filenames to compress. Flags may be clustered (-Ddvb12) or separated + * (-D -d -v -b 12), or combination thereof. Modes and other status is + * copied with copystat(). -O bug for 4.2 seems to have disappeared with + * 1.8. + * + * Revision 1.8 84/08/09 23:15:00 joe + * Made it compatible with vax version, installed jim's fixes/enhancements + * + * Revision 1.6 84/08/01 22:08:00 joe + * Sped up algorithm significantly by sorting the compress chain. + * + * Revision 1.5 84/07/13 13:11:00 srd + * Added C version of vax asm routines. Changed structure to arrays to + * save much memory. Do unsigned compares where possible (faster on + * Perkin-Elmer) + * + * Revision 1.4 84/07/05 03:11:11 thomas + * Clean up the code a little and lint it. (Lint complains about all + * the regs used in the asm, but I'm not going to "fix" this.) + * + * Revision 1.3 84/07/05 02:06:54 thomas + * Minor fixes. + * + * Revision 1.2 84/07/05 00:27:27 thomas + * Add variable bit length output. + * + */ + +static char rcs_ident[] = + "$Hdr: compress.c,v 4.0 85/07/30 12:50:00 joe Release $"; diff --git a/usr.bin/compress/zopen.3 b/usr.bin/compress/zopen.3 new file mode 100644 index 0000000..32be9c4 --- /dev/null +++ b/usr.bin/compress/zopen.3 @@ -0,0 +1,138 @@ +.\" $NetBSD: zopen.3,v 1.7 2003/08/07 11:13:29 agc Exp $ +.\" +.\" Copyright (c) 1992, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)zopen.3 8.1 (Berkeley) 6/9/93 +.\" +.Dd June 9, 1993 +.Dt ZOPEN 3 +.Os +.Sh NAME +.Nm zopen +.Nd compressed stream open function +.Sh SYNOPSIS +.In stdio.h +.Ft FILE * +.Fn zopen "const char *path" "const char *mode" "int bits" +.Sh DESCRIPTION +The +.Fn zopen +function +opens the compressed file whose name is the string pointed to by +.Fa path +and associates a stream with it. +.Pp +The argument +.Fa mode +points to one of the following one-character strings: +.Bl -tag -width indent +.It Dq Li r +Open compressed file for reading. +The stream is positioned at the beginning of the file. +.It Dq Li w +Truncate file to zero length or create compressed file for writing. +The stream is positioned at the beginning of the file. +.El +.Pp +Any created files will have mode +.Pf \\*q Dv S_IRUSR +\&| +.Dv S_IWUSR +\&| +.Dv S_IRGRP +\&| +.Dv S_IWGRP +\&| +.Dv S_IROTH +\&| +.Dv S_IWOTH Ns \\*q +.Pq Li 0666 , +as modified by the process' +umask value (see +.Xr umask 2 ) . +.Pp +Files may only be read or written. +Seek operations are not allowed. +.Pp +The +.Fa bits +argument, if non-zero, is set to the bits code limit. +If zero, the default is 16. +See +.Fn compress 1 +for more information. +.Sh RETURN VALUES +Upon successful completion +.Fn zopen +returns a +.Tn FILE +pointer. +Otherwise, +.Dv NULL +is returned and the global variable +.Va errno +is set to indicate the error. +.Sh ERRORS +.Bl -tag -width [EINVAL] +.It Bq Er EINVAL +The +.Fa mode +or +.Fa bits +arguments specified to +.Fn zopen +were invalid. +.It Bq Er EFTYPE +The compressed file starts with an invalid header, or the compressed +file is compressed with more bits than can be handled. +.El +.Pp +The +.Fn zopen +function may also fail and set +.Va errno +for any of the errors specified for the routines +.Xr fopen 3 +or +.Xr funopen 3 . +.Sh SEE ALSO +.Xr compress 1 , +.Xr fopen 3 , +.Xr funopen 3 +.Sh HISTORY +The +.Nm zopen +function +first appeared in +.Bx 4.4 . +.Sh BUGS +The +.Fn zopen +function +may not be portable to systems other than +.Bx . diff --git a/usr.bin/compress/zopen.c b/usr.bin/compress/zopen.c new file mode 100644 index 0000000..0d3f178 --- /dev/null +++ b/usr.bin/compress/zopen.c @@ -0,0 +1,699 @@ +/* $NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $ */ + +/*- + * Copyright (c) 1985, 1986, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis and James A. Woods, derived from original + * work by Spencer Thomas and Joseph Orost. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)zopen.c 8.1 (Berkeley) 6/27/93"; +#else +static char rcsid[] = "$NetBSD: zopen.c,v 1.15 2011/08/16 13:55:01 joerg Exp $"; +#endif +#endif /* LIBC_SCCS and not lint */ + +/*- + * fcompress.c - File compression ala IEEE Computer, June 1984. + * + * Compress authors: + * Spencer W. Thomas (decvax!utah-cs!thomas) + * Jim McKie (decvax!mcvax!jim) + * Steve Davies (decvax!vax135!petsd!peora!srd) + * Ken Turkowski (decvax!decwrl!turtlevax!ken) + * James A. Woods (decvax!ihnp4!ames!jaw) + * Joe Orost (decvax!vax135!petsd!joe) + * + * Cleaned up and converted to library returning I/O streams by + * Diomidis Spinellis . + * + * zopen(filename, mode, bits) + * Returns a FILE * that can be used for read or write. The modes + * supported are only "r" and "w". Seeking is not allowed. On + * reading the file is decompressed, on writing it is compressed. + * The output is compatible with compress(1) with 16 bit tables. + * Any file produced by compress(1) can be read. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#define BITS 16 /* Default bits. */ +#define HSIZE 69001 /* 95% occupancy */ + +/* A code_int must be able to hold 2**BITS values of type int, and also -1. */ +typedef long code_int; +typedef long count_int; + +typedef u_char char_type; +static char_type magic_header[] = + {'\037', '\235'}; /* 1F 9D */ + +#define BIT_MASK 0x1f /* Defines for third byte of header. */ +#define BLOCK_MASK 0x80 + +/* + * Masks 0x40 and 0x20 are free. I think 0x20 should mean that there is + * a fourth header byte (for expansion). + */ +#define INIT_BITS 9 /* Initial number of bits/code. */ + +#define MAXCODE(n_bits) ((1 << (n_bits)) - 1) + +struct s_zstate { + FILE *zs_fp; /* File stream for I/O */ + char zs_mode; /* r or w */ + enum { + S_START, S_MIDDLE, S_EOF + } zs_state; /* State of computation */ + int zs_n_bits; /* Number of bits/code. */ + int zs_maxbits; /* User settable max # bits/code. */ + code_int zs_maxcode; /* Maximum code, given n_bits. */ + code_int zs_maxmaxcode; /* Should NEVER generate this code. */ + count_int zs_htab [HSIZE]; + u_short zs_codetab [HSIZE]; + code_int zs_hsize; /* For dynamic table sizing. */ + code_int zs_free_ent; /* First unused entry. */ + /* + * Block compression parameters -- after all codes are used up, + * and compression rate changes, start over. + */ + int zs_block_compress; + int zs_clear_flg; + long zs_ratio; + count_int zs_checkpoint; + int zs_offset; + long zs_in_count; /* Length of input. */ + long zs_bytes_out; /* Length of compressed output. */ + long zs_out_count; /* # of codes output (for debugging). */ + char_type zs_buf[BITS]; + union { + struct { + long zs_fcode; + code_int zs_ent; + code_int zs_hsize_reg; + int zs_hshift; + } w; /* Write paramenters */ + struct { + char_type *zs_stackp; + int zs_finchar; + code_int zs_code, zs_oldcode, zs_incode; + int zs_roffset, zs_size; + char_type zs_gbuf[BITS]; + } r; /* Read parameters */ + } u; +}; + +/* Definitions to retain old variable names */ +#define fp zs->zs_fp +#define zmode zs->zs_mode +#define state zs->zs_state +#define n_bits zs->zs_n_bits +#define maxbits zs->zs_maxbits +#define maxcode zs->zs_maxcode +#define maxmaxcode zs->zs_maxmaxcode +#define htab zs->zs_htab +#define codetab zs->zs_codetab +#define hsize zs->zs_hsize +#define free_ent zs->zs_free_ent +#define block_compress zs->zs_block_compress +#define clear_flg zs->zs_clear_flg +#define ratio zs->zs_ratio +#define checkpoint zs->zs_checkpoint +#define offset zs->zs_offset +#define in_count zs->zs_in_count +#define bytes_out zs->zs_bytes_out +#define out_count zs->zs_out_count +#define buf zs->zs_buf +#define fcode zs->u.w.zs_fcode +#define hsize_reg zs->u.w.zs_hsize_reg +#define ent zs->u.w.zs_ent +#define hshift zs->u.w.zs_hshift +#define stackp zs->u.r.zs_stackp +#define finchar zs->u.r.zs_finchar +#define code zs->u.r.zs_code +#define oldcode zs->u.r.zs_oldcode +#define incode zs->u.r.zs_incode +#define roffset zs->u.r.zs_roffset +#define size zs->u.r.zs_size +#define gbuf zs->u.r.zs_gbuf + +/* + * To save much memory, we overlay the table used by compress() with those + * used by decompress(). The tab_prefix table is the same size and type as + * the codetab. The tab_suffix table needs 2**BITS characters. We get this + * from the beginning of htab. The output stack uses the rest of htab, and + * contains characters. There is plenty of room for any possible stack + * (stack used to be 8000 characters). + */ + +#define htabof(i) htab[i] +#define codetabof(i) codetab[i] + +#define tab_prefixof(i) codetabof(i) +#define tab_suffixof(i) ((char_type *)(htab))[i] +#define de_stack ((char_type *)&tab_suffixof(1 << BITS)) + +#define CHECK_GAP 10000 /* Ratio check interval. */ + +/* + * the next two codes should not be changed lightly, as they must not + * lie within the contiguous general code space. + */ +#define FIRST 257 /* First free entry. */ +#define CLEAR 256 /* Table clear output code. */ + +static int cl_block(struct s_zstate *); +static code_int getcode(struct s_zstate *); +static int output(struct s_zstate *, code_int); +static int zclose(void *); +FILE *zopen(const char *, const char *, int); +static int zread(void *, char *, int); +static int zwrite(void *, const char *, int); + +/*- + * Algorithm from "A Technique for High Performance Data Compression", + * Terry A. Welch, IEEE Computer Vol 17, No 6 (June 1984), pp 8-19. + * + * Algorithm: + * Modified Lempel-Ziv method (LZW). Basically finds common + * substrings and replaces them with a variable size code. This is + * deterministic, and can be done on the fly. Thus, the decompression + * procedure needs no input table, but tracks the way the table was built. + */ + +/*- + * compress write + * + * Algorithm: use open addressing double hashing (no chaining) on the + * prefix code / next character combination. We do a variant of Knuth's + * algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime + * secondary probe. Here, the modular division first probe is gives way + * to a faster exclusive-or manipulation. Also do block compression with + * an adaptive reset, whereby the code table is cleared when the compression + * ratio decreases, but after the table fills. The variable-length output + * codes are re-sized at this point, and a special CLEAR code is generated + * for the decompressor. Late addition: construct the table according to + * file size for noticeable speed improvement on small files. Please direct + * questions about this implementation to ames!jaw. + */ +static int +zwrite(void *cookie, const char *wbp, int num) +{ + code_int i; + int c, disp; + struct s_zstate *zs; + const u_char *bp; + u_char tmp; + int count; + + if (num == 0) + return (0); + + zs = cookie; + count = num; + bp = (const u_char *)wbp; + if (state == S_MIDDLE) + goto middle; + state = S_MIDDLE; + + maxmaxcode = 1L << maxbits; + if (fwrite(magic_header, + sizeof(char), sizeof(magic_header), fp) != sizeof(magic_header)) + return (-1); + tmp = (u_char)(maxbits | block_compress); + if (fwrite(&tmp, sizeof(char), sizeof(tmp), fp) != sizeof(tmp)) + return (-1); + + offset = 0; + bytes_out = 3; /* Includes 3-byte header mojo. */ + out_count = 0; + clear_flg = 0; + ratio = 0; + in_count = 1; + checkpoint = CHECK_GAP; + maxcode = MAXCODE(n_bits = INIT_BITS); + free_ent = ((block_compress) ? FIRST : 256); + + ent = *bp++; + --count; + + hshift = 0; + for (fcode = (long)hsize; fcode < 65536L; fcode *= 2L) + hshift++; + hshift = 8 - hshift; /* Set hash code range bound. */ + + hsize_reg = hsize; + memset(htab, 0xff, hsize_reg * sizeof(count_int)); + +middle: while (count--) { + c = *bp++; + in_count++; + fcode = (long)(((long)c << maxbits) + ent); + i = ((c << hshift) ^ ent); /* Xor hashing. */ + + if (htabof(i) == fcode) { + ent = codetabof(i); + continue; + } else if ((long)htabof(i) < 0) /* Empty slot. */ + goto nomatch; + disp = hsize_reg - i; /* Secondary hash (after G. Knott). */ + if (i == 0) + disp = 1; +probe: if ((i -= disp) < 0) + i += hsize_reg; + + if (htabof(i) == fcode) { + ent = codetabof(i); + continue; + } + if ((long)htabof(i) >= 0) + goto probe; +nomatch: if (output(zs, (code_int) ent) == -1) + return (-1); + out_count++; + ent = c; + if (free_ent < maxmaxcode) { + codetabof(i) = free_ent++; /* code -> hashtable */ + htabof(i) = fcode; + } else if ((count_int)in_count >= + checkpoint && block_compress) { + if (cl_block(zs) == -1) + return (-1); + } + } + return (num); +} + +static int +zclose(void *cookie) +{ + struct s_zstate *zs; + int rval; + + zs = cookie; + if (zmode == 'w') { /* Put out the final code. */ + if (output(zs, (code_int) ent) == -1) { + (void)fclose(fp); + free(zs); + return (-1); + } + out_count++; + if (output(zs, (code_int) - 1) == -1) { + (void)fclose(fp); + free(zs); + return (-1); + } + } + rval = fclose(fp) == EOF ? -1 : 0; + free(zs); + return (rval); +} + +/*- + * Output the given code. + * Inputs: + * code: A n_bits-bit integer. If == -1, then EOF. This assumes + * that n_bits =< (long)wordsize - 1. + * Outputs: + * Outputs code to the file. + * Assumptions: + * Chars are 8 bits long. + * Algorithm: + * Maintain a BITS character long buffer (so that 8 codes will + * fit in it exactly). Use the VAX insv instruction to insert each + * code in turn. When the buffer fills up empty it and start over. + */ + +static char_type lmask[9] = + {0xff, 0xfe, 0xfc, 0xf8, 0xf0, 0xe0, 0xc0, 0x80, 0x00}; +static char_type rmask[9] = + {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff}; + +static int +output(struct s_zstate *zs, code_int ocode) +{ + int bits, r_off; + char_type *bp; + + r_off = offset; + bits = n_bits; + bp = buf; + if (ocode >= 0) { + /* Get to the first byte. */ + bp += (r_off >> 3); + r_off &= 7; + /* + * Since ocode is always >= 8 bits, only need to mask the first + * hunk on the left. + */ + *bp = (*bp & rmask[r_off]) | ((ocode << r_off) & lmask[r_off]); + bp++; + bits -= (8 - r_off); + ocode >>= 8 - r_off; + /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */ + if (bits >= 8) { + *bp++ = ocode; + ocode >>= 8; + bits -= 8; + } + /* Last bits. */ + if (bits) + *bp = ocode; + offset += n_bits; + if (offset == (n_bits << 3)) { + bp = buf; + bits = n_bits; + bytes_out += bits; + if (fwrite(bp, sizeof(char), bits, fp) != (size_t)bits) + return (-1); + bp += bits; + bits = 0; + offset = 0; + } + /* + * If the next entry is going to be too big for the ocode size, + * then increase it, if possible. + */ + if (free_ent > maxcode || (clear_flg > 0)) { + /* + * Write the whole buffer, because the input side won't + * discover the size increase until after it has read it. + */ + if (offset > 0) { + if (fwrite(buf, 1, n_bits, fp) != (size_t)n_bits) + return (-1); + bytes_out += n_bits; + } + offset = 0; + + if (clear_flg) { + maxcode = MAXCODE(n_bits = INIT_BITS); + clear_flg = 0; + } else { + n_bits++; + if (n_bits == maxbits) + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + } + } else { + /* At EOF, write the rest of the buffer. */ + if (offset > 0) { + offset = (offset + 7) / 8; + if (fwrite(buf, 1, offset, fp) != (size_t)offset) + return (-1); + bytes_out += offset; + } + offset = 0; + } + return (0); +} + +/* + * Decompress read. This routine adapts to the codes in the file building + * the "string" table on-the-fly; requiring no table to be stored in the + * compressed file. The tables used herein are shared with those of the + * compress() routine. See the definitions above. + */ +static int +zread(void *cookie, char *rbp, int num) +{ + u_int count; + struct s_zstate *zs; + u_char *bp, header[3]; + + if (num == 0) + return (0); + + zs = cookie; + count = num; + bp = (u_char *)rbp; + switch (state) { + case S_START: + state = S_MIDDLE; + break; + case S_MIDDLE: + goto middle; + case S_EOF: + goto eof; + } + + /* Check the magic number */ + if (fread(header, + sizeof(char), sizeof(header), fp) != sizeof(header) || + memcmp(header, magic_header, sizeof(magic_header)) != 0) { + errno = EFTYPE; + return (-1); + } + maxbits = header[2]; /* Set -b from file. */ + block_compress = maxbits & BLOCK_MASK; + maxbits &= BIT_MASK; + maxmaxcode = 1L << maxbits; + if (maxbits > BITS || maxbits < 12) { + errno = EFTYPE; + return (-1); + } + /* As above, initialize the first 256 entries in the table. */ + maxcode = MAXCODE(n_bits = INIT_BITS); + for (code = 255; code >= 0; code--) { + tab_prefixof(code) = 0; + tab_suffixof(code) = (char_type) code; + } + free_ent = block_compress ? FIRST : 256; + oldcode = -1; + stackp = de_stack; + + while ((code = getcode(zs)) > -1) { + + if ((code == CLEAR) && block_compress) { + for (code = 255; code >= 0; code--) + tab_prefixof(code) = 0; + clear_flg = 1; + free_ent = FIRST; + oldcode = -1; + continue; + } + incode = code; + + /* Special case for kWkWk string. */ + if (code >= free_ent) { + if (code > free_ent || oldcode == -1) { + /* Bad stream. */ + errno = EINVAL; + return (-1); + } + *stackp++ = finchar; + code = oldcode; + } + /* + * The above condition ensures that code < free_ent. + * The construction of tab_prefixof in turn guarantees that + * each iteration decreases code and therefore stack usage is + * bound by 1 << BITS - 256. + */ + + /* Generate output characters in reverse order. */ + while (code >= 256) { + *stackp++ = tab_suffixof(code); + code = tab_prefixof(code); + } + *stackp++ = finchar = tab_suffixof(code); + + /* And put them out in forward order. */ +middle: do { + if (count-- == 0) + return (num); + *bp++ = *--stackp; + } while (stackp > de_stack); + + /* Generate the new entry. */ + if ((code = free_ent) < maxmaxcode && oldcode != -1) { + tab_prefixof(code) = (u_short) oldcode; + tab_suffixof(code) = finchar; + free_ent = code + 1; + } + + /* Remember previous code. */ + oldcode = incode; + } + state = S_EOF; +eof: return (num - count); +} + +/*- + * Read one code from the standard input. If EOF, return -1. + * Inputs: + * stdin + * Outputs: + * code or -1 is returned. + */ +static code_int +getcode(struct s_zstate *zs) +{ + code_int gcode; + int r_off, bits; + char_type *bp; + + bp = gbuf; + if (clear_flg > 0 || roffset >= size || free_ent > maxcode) { + /* + * If the next entry will be too big for the current gcode + * size, then we must increase the size. This implies reading + * a new buffer full, too. + */ + if (free_ent > maxcode) { + n_bits++; + if (n_bits == maxbits) /* Won't get any bigger now. */ + maxcode = maxmaxcode; + else + maxcode = MAXCODE(n_bits); + } + if (clear_flg > 0) { + maxcode = MAXCODE(n_bits = INIT_BITS); + clear_flg = 0; + } + size = fread(gbuf, 1, n_bits, fp); + if (size <= 0) /* End of file. */ + return (-1); + roffset = 0; + /* Round size down to integral number of codes. */ + size = (size << 3) - (n_bits - 1); + } + r_off = roffset; + bits = n_bits; + + /* Get to the first byte. */ + bp += (r_off >> 3); + r_off &= 7; + + /* Get first part (low order bits). */ + gcode = (*bp++ >> r_off); + bits -= (8 - r_off); + r_off = 8 - r_off; /* Now, roffset into gcode word. */ + + /* Get any 8 bit parts in the middle (<=1 for up to 16 bits). */ + if (bits >= 8) { + gcode |= *bp++ << r_off; + r_off += 8; + bits -= 8; + } + + /* High order bits. */ + gcode |= (*bp & rmask[bits]) << r_off; + roffset += n_bits; + + return (gcode); +} + +static int +cl_block(struct s_zstate *zs) /* Table clear for block compress. */ +{ + long rat; + + checkpoint = in_count + CHECK_GAP; + + if (in_count > 0x007fffff) { /* Shift will overflow. */ + rat = bytes_out >> 8; + if (rat == 0) /* Don't divide by zero. */ + rat = 0x7fffffff; + else + rat = in_count / rat; + } else + rat = (in_count << 8) / bytes_out; /* 8 fractional bits. */ + if (rat > ratio) + ratio = rat; + else { + ratio = 0; + memset(htab, 0xff, hsize * sizeof(count_int)); + free_ent = FIRST; + clear_flg = 1; + if (output(zs, (code_int) CLEAR) == -1) + return (-1); + } + return (0); +} + +FILE * +zopen(const char *fname, const char *mode, int bits) +{ + struct s_zstate *zs; + + if ((mode[0] != 'r' && mode[0] != 'w') || mode[1] != '\0' || + bits < 0 || bits > BITS) { + errno = EINVAL; + return (NULL); + } + + if ((zs = calloc(1, sizeof(struct s_zstate))) == NULL) + return (NULL); + + maxbits = bits ? bits : BITS; /* User settable max # bits/code. */ + maxmaxcode = 1 << maxbits; /* Should NEVER generate this code. */ + hsize = HSIZE; /* For dynamic table sizing. */ + free_ent = 0; /* First unused entry. */ + block_compress = BLOCK_MASK; + clear_flg = 0; + ratio = 0; + checkpoint = CHECK_GAP; + in_count = 1; /* Length of input. */ + out_count = 0; /* # of codes output (for debugging). */ + state = S_START; + roffset = 0; + size = 0; + + /* + * Layering compress on top of stdio in order to provide buffering, + * and ensure that reads and write work with the data specified. + */ + if ((fp = fopen(fname, mode)) == NULL) { + free(zs); + return (NULL); + } + switch (*mode) { + case 'r': + zmode = 'r'; + return (funopen(zs, zread, NULL, NULL, zclose)); + case 'w': + zmode = 'w'; + return (funopen(zs, NULL, zwrite, NULL, zclose)); + } + /* NOTREACHED */ + return (NULL); +} diff --git a/usr.bin/csplit/csplit.1 b/usr.bin/csplit/csplit.1 new file mode 100644 index 0000000..93f96a6 --- /dev/null +++ b/usr.bin/csplit/csplit.1 @@ -0,0 +1,163 @@ +.\" $NetBSD: csplit.1,v 1.5 2014/02/04 19:48:48 wiz Exp $ +.\" +.\" Copyright (c) 2002 Tim J. Robbins. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD: src/usr.bin/csplit/csplit.1,v 1.11 2005/01/25 22:29:51 tjr Exp $ +.\" +.Dd February 4, 2014 +.Dt CSPLIT 1 +.Os +.Sh NAME +.Nm csplit +.Nd split files based on context +.Sh SYNOPSIS +.Nm +.Op Fl ks +.Op Fl f Ar prefix +.Op Fl n Ar number +.Ar file args ... +.Sh DESCRIPTION +The +.Nm +utility splits +.Ar file +into pieces using the patterns +.Ar args . +If +.Ar file +is +a dash +.Pq Sq - , +.Nm +reads from standard input. +.Pp +Files are created with a prefix of +.Dq xx +and two decimal digits. +The size of each file is written to standard output +as it is created. +If an error occurs whilst files are being created, +or a +.Dv HUP , +.Dv INT , +or +.Dv TERM +signal is received, +all files previously written are removed. +.Pp +The options are as follows: +.Bl -tag -offset indent -width 10n +.It Fl f Ar prefix +Create file names beginning with +.Ar prefix , +instead of +.Dq xx . +.It Fl k +Do not remove previously created files if an error occurs or a +.Dv HUP , +.Dv INT , +or +.Dv TERM +signal is received. +.It Fl n Ar number +Create file names beginning with +.Ar number +of decimal digits after the prefix, +instead of 2. +.It Fl s +Do not write the size of each output file to standard output as it is +created. +.El +.Pp +The +.Ar args +operands may be a combination of the following patterns: +.Bl -tag -offset indent -width 10n +.It / Ns Ar regexp Ns / Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc +Create a file containing the input from the current line to (but not including) +the next line matching the given basic regular expression. +An optional +.Ar offset +from the line that matched may be specified. +.It % Ns Ar regexp Ns % Ns Oo Oo Cm + Ns | Ns Cm - Oc Ns Ar offset Oc +Same as above but a file is not created for the output. +.It Ar line_no +Create containing the input from the current line to (but not including) +the specified line number. +.It Brq Ar num +Repeat the previous pattern the specified number of times. +If it follows a line number pattern, a new file will be created for each +.Ar line_no +lines, +.Ar num +times. +The first line of the file is line number 1 for historic reasons. +.El +.Pp +After all the patterns have been processed, the remaining input data +(if there is any) will be written to a new file. +.Pp +Requesting to split at a line before the current line number or past the +end of the file will result in an error. +.Sh ENVIRONMENT +The +.Ev LANG , LC_ALL , LC_COLLATE , +and +.Ev LC_CTYPE +environment variables affect the execution of +.Nm +as described in +.Xr environ 7 . +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +Split the +.Xr mdoc 7 +file +.Pa foo.1 +into one file for each section (up to 21 plus one for the rest, if any): +.Pp +.Dl "$ csplit -k foo.1 '%^\e.Sh%' '/^\e.Sh/' '{20}'" +.Pp +Split standard input after the first 99 lines and every 100 lines thereafter: +.Pp +.Dl "$ csplit -k - 100 '{19}'" +.Sh SEE ALSO +.Xr sed 1 , +.Xr split 1 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2004 . +.Sh HISTORY +A +.Nm +command appeared in PWB UNIX. +.Sh BUGS +Input lines are limited to +.Dv LINE_MAX +(2048) bytes in length. diff --git a/usr.bin/csplit/csplit.c b/usr.bin/csplit/csplit.c new file mode 100644 index 0000000..9308ed8 --- /dev/null +++ b/usr.bin/csplit/csplit.c @@ -0,0 +1,479 @@ +/* $NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $ */ +/* $FreeBSD: src/usr.bin/csplit/csplit.c,v 1.9 2004/03/22 11:15:03 tjr Exp$ */ + +/*- + * Copyright (c) 2002 Tim J. Robbins. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * csplit -- split files based on context + * + * This utility splits its input into numbered output files by line number + * or by a regular expression. Regular expression matches have an optional + * offset with them, allowing the split to occur a specified number of + * lines before or after the match. + * + * To handle negative offsets, we stop reading when the match occurs and + * store the offset that the file should have been split at, then use + * this output file as input until all the "overflowed" lines have been read. + * The file is then closed and truncated to the correct length. + * + * We assume that the output files can be seeked upon (ie. they cannot be + * symlinks to named pipes or character devices), but make no such + * assumption about the input. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: csplit.c,v 1.7 2017/07/30 23:02:53 cheusov Exp $"); +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void cleanup(void); +static void do_lineno(const char *); +static void do_rexp(const char *); +static char *get_line(void); +static void handlesig(int); +static FILE *newfile(void); +static void toomuch(FILE *, long); +static void usage(void) __dead; + +/* + * Command line options + */ +static const char *prefix; /* File name prefix */ +static long sufflen; /* Number of decimal digits for suffix */ +static int sflag; /* Suppress output of file names */ +static int kflag; /* Keep output if error occurs */ + +/* + * Other miscellaneous globals (XXX too many) + */ +static long lineno; /* Current line number in input file */ +static long reps; /* Number of repetitions for this pattern */ +static long nfiles; /* Number of files output so far */ +static long maxfiles; /* Maximum number of files we can create */ +static char currfile[PATH_MAX]; /* Current output file */ +static const char *infn; /* Name of the input file */ +static FILE *infile; /* Input file handle */ +static FILE *overfile; /* Overflow file for toomuch() */ +static off_t truncofs; /* Offset this file should be truncated at */ +static int doclean; /* Should cleanup() remove output? */ + +int +main(int argc, char *argv[]) +{ + struct sigaction sa; + long i; + int ch; + const char *expr; + char *ep, *p; + FILE *ofp; + + (void)setlocale(LC_ALL, ""); + + kflag = sflag = 0; + prefix = "xx"; + sufflen = 2; + while ((ch = getopt(argc, argv, "ksf:n:")) > 0) { + switch (ch) { + case 'f': + prefix = optarg; + break; + case 'k': + kflag = 1; + break; + case 'n': + errno = 0; + sufflen = strtol(optarg, &ep, 10); + if (sufflen <= 0 || *ep != '\0' || errno != 0) + errx(1, "%s: bad suffix length", optarg); + break; + case 's': + sflag = 1; + break; + default: + usage(); + /*NOTREACHED*/ + } + } + + if (sufflen + strlen(prefix) >= PATH_MAX) + errx(1, "name too long"); + + argc -= optind; + argv += optind; + + if ((infn = *argv++) == NULL) + usage(); + if (strcmp(infn, "-") == 0) { + infile = stdin; + infn = "stdin"; + } else if ((infile = fopen(infn, "r")) == NULL) + err(1, "%s", infn); + + if (!kflag) { + doclean = 1; + (void)atexit(cleanup); + sa.sa_flags = 0; + sa.sa_handler = handlesig; + (void)sigemptyset(&sa.sa_mask); + (void)sigaddset(&sa.sa_mask, SIGHUP); + (void)sigaddset(&sa.sa_mask, SIGINT); + (void)sigaddset(&sa.sa_mask, SIGTERM); + (void)sigaction(SIGHUP, &sa, NULL); + (void)sigaction(SIGINT, &sa, NULL); + (void)sigaction(SIGTERM, &sa, NULL); + } + + lineno = 0; + nfiles = 0; + truncofs = 0; + overfile = NULL; + + /* Ensure 10^sufflen < LONG_MAX. */ + for (maxfiles = 1, i = 0; i < sufflen; i++) { + if (maxfiles > LONG_MAX / 10) + errx(1, "%ld: suffix too long (limit %ld)", + sufflen, i); + maxfiles *= 10; + } + + /* Create files based on supplied patterns. */ + while (nfiles < maxfiles - 1 && (expr = *argv++) != NULL) { + /* Look ahead & see if this pattern has any repetitions. */ + if (*argv != NULL && **argv == '{') { + errno = 0; + reps = strtol(*argv + 1, &ep, 10); + if (reps < 0 || *ep != '}' || errno != 0) + errx(1, "%s: bad repetition count", *argv + 1); + argv++; + } else + reps = 0; + + if (*expr == '/' || *expr == '%') { + do + do_rexp(expr); + while (reps-- != 0 && nfiles < maxfiles - 1); + } else if (isdigit((unsigned char)*expr)) + do_lineno(expr); + else + errx(1, "%s: unrecognised pattern", expr); + } + + /* Copy the rest into a new file. */ + if (!feof(infile)) { + ofp = newfile(); + while ((p = get_line()) != NULL && fputs(p, ofp) != EOF) + ; + if (!sflag) + (void)printf("%jd\n", (intmax_t)ftello(ofp)); + if (fclose(ofp) != 0) + err(1, "%s", currfile); + } + + toomuch(NULL, 0L); + doclean = 0; + + return (0); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, +"Usage: %s [-ks] [-f prefix] [-n number] file args ...\n", getprogname()); + exit(1); +} + +__dead static void +handlesig(int sig) +{ + char msg[BUFSIZ]; + size_t len; + + len = snprintf(msg, sizeof(msg), "%s: Caught %s, cleaning up\n", + getprogname(), strsignal(sig)); + if (len < sizeof(msg)) + (void)write(STDERR_FILENO, msg, len); + cleanup(); + (void)raise_default_signal(sig); + _exit(2); +} + +/* Create a new output file. */ +static FILE * +newfile(void) +{ + FILE *fp; + + if ((size_t)snprintf(currfile, sizeof(currfile), "%s%0*ld", prefix, + (int)sufflen, nfiles) >= sizeof(currfile)) + errx(1, "%s: %s", currfile, strerror(ENAMETOOLONG)); + if ((fp = fopen(currfile, "w+")) == NULL) + err(1, "%s", currfile); + nfiles++; + + return (fp); +} + +/* Remove partial output, called before exiting. */ +static void +cleanup(void) +{ + char fnbuf[PATH_MAX]; + long i; + + if (!doclean) + return; + + /* + * NOTE: One cannot portably assume to be able to call snprintf() + * from inside a signal handler. It does, however, appear to be safe + * to do on FreeBSD and NetBSD. The solution to this problem is worse + * than the problem itself. + */ + + for (i = 0; i < nfiles; i++) { + (void)snprintf(fnbuf, sizeof(fnbuf), "%s%0*ld", prefix, + (int)sufflen, i); + (void)unlink(fnbuf); + } +} + +/* Read a line from the input into a static buffer. */ +static char * +get_line(void) +{ + static char lbuf[LINE_MAX]; + FILE *src; + + src = overfile != NULL ? overfile : infile; + +again: if (fgets(lbuf, sizeof(lbuf), src) == NULL) { + if (src == overfile) { + src = infile; + goto again; + } + return (NULL); + } + if (ferror(src)) + err(1, "%s", infn); + lineno++; + + return (lbuf); +} + +/* Conceptually rewind the input (as obtained by get_line()) back `n' lines. */ +static void +toomuch(FILE *ofp, long n) +{ + char buf[BUFSIZ]; + size_t i, nread; + + if (overfile != NULL) { + /* + * Truncate the previous file we overflowed into back to + * the correct length, close it. + */ + if (fflush(overfile) != 0) + err(1, "overflow"); + if (ftruncate(fileno(overfile), truncofs) != 0) + err(1, "overflow"); + if (fclose(overfile) != 0) + err(1, "overflow"); + overfile = NULL; + } + + if (n == 0) + /* Just tidying up */ + return; + + lineno -= n; + + /* + * Wind the overflow file backwards to `n' lines before the + * current one. + */ + do { + if (ftello(ofp) < (off_t)sizeof(buf)) + rewind(ofp); + else + (void)fseeko(ofp, -(off_t)sizeof(buf), SEEK_CUR); + if (ferror(ofp)) + errx(1, "%s: can't seek", currfile); + if ((nread = fread(buf, 1, sizeof(buf), ofp)) == 0) + errx(1, "can't read overflowed output"); + if (fseeko(ofp, -(off_t)nread, SEEK_CUR) != 0) + err(1, "%s", currfile); + for (i = 1; i <= nread; i++) + if (buf[nread - i] == '\n' && n-- == 0) + break; + if (ftello(ofp) == 0) + break; + } while (n > 0); + if (fseeko(ofp, (off_t)nread - i + 1, SEEK_CUR) != 0) + err(1, "%s", currfile); + + /* + * get_line() will read from here. Next call will truncate to + * truncofs in this file. + */ + overfile = ofp; + truncofs = ftello(overfile); +} + +/* Handle splits for /regexp/ and %regexp% patterns. */ +static void +do_rexp(const char *expr) +{ + regex_t cre; + intmax_t nwritten; + long ofs; + int first; + char *ecopy, *ep, *p, *pofs, *re; + FILE *ofp; + + if ((ecopy = strdup(expr)) == NULL) + err(1, "strdup"); + + re = ecopy + 1; + if ((pofs = strrchr(ecopy, *expr)) == NULL || pofs[-1] == '\\') + errx(1, "%s: missing trailing %c", expr, *expr); + *pofs++ = '\0'; + + if (*pofs != '\0') { + errno = 0; + ofs = strtol(pofs, &ep, 10); + if (*ep != '\0' || errno != 0) + errx(1, "%s: bad offset", pofs); + } else + ofs = 0; + + if (regcomp(&cre, re, REG_BASIC|REG_NOSUB) != 0) + errx(1, "%s: bad regular expression", re); + + if (*expr == '/') + /* /regexp/: Save results to a file. */ + ofp = newfile(); + else { + /* %regexp%: Make a temporary file for overflow. */ + if ((ofp = tmpfile()) == NULL) + err(1, "tmpfile"); + } + + /* Read and output lines until we get a match. */ + first = 1; + while ((p = get_line()) != NULL) { + if (fputs(p, ofp) == EOF) + break; + if (!first && regexec(&cre, p, 0, NULL, 0) == 0) + break; + first = 0; + } + + if (p == NULL) + errx(1, "%s: no match", re); + + if (ofs <= 0) { + /* + * Negative (or zero) offset: throw back any lines we should + * not have read yet. + */ + if (p != NULL) { + toomuch(ofp, -ofs + 1); + nwritten = (intmax_t)truncofs; + } else + nwritten = (intmax_t)ftello(ofp); + } else { + /* + * Positive offset: copy the requested number of lines + * after the match. + */ + while (--ofs > 0 && (p = get_line()) != NULL) + if (fputs(p, ofp) == EOF) + break; + toomuch(NULL, 0L); + nwritten = (intmax_t)ftello(ofp); + if (fclose(ofp) != 0) + err(1, "%s", currfile); + } + + if (!sflag && *expr == '/') + (void)printf("%jd\n", nwritten); + + regfree(&cre); + free(ecopy); +} + +/* Handle splits based on line number. */ +static void +do_lineno(const char *expr) +{ + long lastline, tgtline; + char *ep, *p; + FILE *ofp; + + errno = 0; + tgtline = strtol(expr, &ep, 10); + if (tgtline <= 0 || errno != 0 || *ep != '\0') + errx(1, "%s: bad line number", expr); + lastline = tgtline; + if (lastline <= lineno) + errx(1, "%s: can't go backwards", expr); + + while (nfiles < maxfiles - 1) { + ofp = newfile(); + while (lineno + 1 != lastline) { + if ((p = get_line()) == NULL) + errx(1, "%ld: out of range", lastline); + if (fputs(p, ofp) == EOF) + break; + } + if (!sflag) + (void)printf("%jd\n", (intmax_t)ftello(ofp)); + if (fclose(ofp) != 0) + err(1, "%s", currfile); + if (reps-- == 0) + break; + lastline += tgtline; + } +} diff --git a/usr.bin/ctags/C.c b/usr.bin/ctags/C.c new file mode 100644 index 0000000..9d32b22 --- /dev/null +++ b/usr.bin/ctags/C.c @@ -0,0 +1,573 @@ +/* $NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)C.c 8.4 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: C.c,v 1.19 2009/07/13 19:05:40 roy Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include "ctags.h" + +static int func_entry(void); +static void hash_entry(void); +static void skip_string(int); +static int str_entry(int); + +/* + * c_entries -- + * read .c and .h files and call appropriate routines + */ +void +c_entries(void) +{ + int c; /* current character */ + int level; /* brace level */ + int token; /* if reading a token */ + int t_def; /* if reading a typedef */ + int t_level; /* typedef's brace level */ + char *sp; /* buffer pointer */ + char tok[MAXTOKEN]; /* token buffer */ + + lineftell = ftell(inf); + sp = tok; token = t_def = NO; t_level = -1; level = 0; lineno = 1; + while (GETC(!=, EOF)) { + switch (c) { + /* + * Here's where it DOESN'T handle: { + * foo(a) + * { + * #ifdef notdef + * } + * #endif + * if (a) + * puts("hello, world"); + * } + */ + case '{': + ++level; + goto endtok; + case '}': + /* + * if level goes below zero, try and fix + * it, even though we've already messed up + */ + if (--level < 0) + level = 0; + goto endtok; + + case '\n': + SETLINE; + /* + * the above 3 cases are similar in that they + * are special characters that also end tokens. + */ + endtok: if (sp > tok) { + *sp = EOS; + token = YES; + sp = tok; + } + else + token = NO; + continue; + + /* + * We ignore quoted strings and character constants + * completely. + */ + case '"': + case '\'': + (void)skip_string(c); + break; + + /* + * comments can be fun; note the state is unchanged after + * return, in case we found: + * "foo() XX comment XX { int bar; }" + */ + case '/': + if (GETC(==, '*')) { + skip_comment(c); + continue; + } else if (c == '/') { + skip_comment(c); + continue; + } + (void)ungetc(c, inf); + c = '/'; + goto storec; + + /* hash marks flag #define's. */ + case '#': + if (sp == tok) { + hash_entry(); + break; + } + goto storec; + + /* + * if we have a current token, parenthesis on + * level zero indicates a function. + */ + case '(': + do c = getc(inf); + while (c != EOF && iswhite(c)); + if (c == '*') + break; + if (c != EOF) + ungetc(c, inf); + if (!level && token) { + int curline; + + if (sp != tok) + *sp = EOS; + /* + * grab the line immediately, we may + * already be wrong, for example, + * foo\n + * (arg1, + */ + get_line(); + curline = lineno; + if (func_entry()) { + ++level; + pfnote(tok, curline); + } + break; + } + goto storec; + + /* + * semi-colons indicate the end of a typedef; if we find a + * typedef we search for the next semi-colon of the same + * level as the typedef. Ignoring "structs", they are + * tricky, since you can find: + * + * "typedef long time_t;" + * "typedef unsigned int u_int;" + * "typedef unsigned int u_int [10];" + * + * If looking at a typedef, we save a copy of the last token + * found. Then, when we find the ';' we take the current + * token if it starts with a valid token name, else we take + * the one we saved. There's probably some reasonable + * alternative to this... + */ + case ';': + if (t_def && level == t_level) { + t_def = NO; + get_line(); + if (sp != tok) + *sp = EOS; + pfnote(tok, lineno); + break; + } + goto storec; + + /* + * store characters until one that can't be part of a token + * comes along; check the current token against certain + * reserved words. + */ + default: + storec: if (c == EOF) + break; + if (!intoken(c)) { + if (sp == tok) + break; + *sp = EOS; + if (tflag) { + /* no typedefs inside typedefs */ + if (!t_def && + !memcmp(tok, "typedef",8)) { + t_def = YES; + t_level = level; + break; + } + /* catch "typedef struct" */ + if ((!t_def || t_level <= level) + && (!memcmp(tok, "struct", 7) + || !memcmp(tok, "union", 6) + || !memcmp(tok, "enum", 5))) { + /* + * get line immediately; + * may change before '{' + */ + get_line(); + if (str_entry(c)) + ++level; + break; + /* } */ + } + } + sp = tok; + } + else if (sp != tok || begtoken(c)) { + if (sp < tok + sizeof tok) + *sp++ = c; + token = YES; + } + continue; + } + + sp = tok; + token = NO; + } +} + +/* + * func_entry -- + * handle a function reference + */ +static int +func_entry(void) +{ + int c; /* current character */ + int level = 0; /* for matching '()' */ + static char attribute[] = "__attribute__"; + char maybe_attribute[sizeof attribute + 1], + *anext; + + /* + * Find the end of the assumed function declaration. + * Note that ANSI C functions can have type definitions so keep + * track of the parentheses nesting level. + */ + while (GETC(!=, EOF)) { + switch (c) { + case '\'': + case '"': + /* skip strings and character constants */ + skip_string(c); + break; + case '/': + /* skip comments */ + if (GETC(==, '*')) + skip_comment(c); + else if (c == '/') + skip_comment(c); + break; + case '(': + level++; + break; + case ')': + if (level == 0) + goto fnd; + level--; + break; + case '\n': + SETLINE; + } + } + return (NO); +fnd: + /* + * we assume that the character after a function's right paren + * is a token character if it's a function and a non-token + * character if it's a declaration. Comments don't count... + */ + for (anext = maybe_attribute;;) { + while (GETC(!=, EOF) && iswhite(c)) + if (c == '\n') + SETLINE; + if (c == EOF) + return NO; + /* + * Recognize the gnu __attribute__ extension, which would + * otherwise make the heuristic test DTWT + */ + if (anext == maybe_attribute) { + if (intoken(c)) { + *anext++ = c; + continue; + } + } else { + if (intoken(c)) { + if (anext - maybe_attribute + < (ptrdiff_t)(sizeof attribute - 1)) + *anext++ = c; + else break; + continue; + } else { + *anext++ = '\0'; + if (strcmp(maybe_attribute, attribute) == 0) { + (void)ungetc(c, inf); + return NO; + } + break; + } + } + if (intoken(c) || c == '{') + break; + if (c == '/' && GETC(==, '*')) + skip_comment(c); + else if (c == '/') + skip_comment(c); + else { /* don't ever "read" '/' */ + (void)ungetc(c, inf); + return (NO); + } + } + if (c != '{') + (void)skip_key('{'); + return (YES); +} + +/* + * hash_entry -- + * handle a line starting with a '#' + */ +static void +hash_entry(void) +{ + int c; /* character read */ + int curline; /* line started on */ + char *sp; /* buffer pointer */ + char tok[MAXTOKEN]; /* storage buffer */ + + curline = lineno; + do if (GETC(==, EOF)) + return; + while(c != '\n' && iswhite(c)); + ungetc(c, inf); + for (sp = tok;;) { /* get next token */ + if (GETC(==, EOF)) + return; + if (iswhite(c)) + break; + if (sp < tok + sizeof tok) + *sp++ = c; + } + if(sp >= tok + sizeof tok) + --sp; + *sp = EOS; + if (memcmp(tok, "define", 6)) /* only interested in #define's */ + goto skip; + for (;;) { /* this doesn't handle "#define \n" */ + if (GETC(==, EOF)) + return; + if (!iswhite(c)) + break; + } + for (sp = tok;;) { /* get next token */ + if(sp < tok + sizeof tok) + *sp++ = c; + if (GETC(==, EOF)) + return; + /* + * this is where it DOESN'T handle + * "#define \n" + */ + if (!intoken(c)) + break; + } + if(sp >= tok + sizeof tok) + --sp; + *sp = EOS; + if (dflag || c == '(') { /* only want macros */ + get_line(); + pfnote(tok, curline); + } +skip: if (c == '\n') { /* get rid of rest of define */ + SETLINE + if (*(sp - 1) != '\\') + return; + } + (void)skip_key('\n'); +} + +/* + * str_entry -- + * handle a struct, union or enum entry + */ +static int +str_entry(int c /* current character */) +{ + int curline; /* line started on */ + char *sp; /* buffer pointer */ + char tok[LINE_MAX]; /* storage buffer */ + + curline = lineno; + while (iswhite(c)) + if (GETC(==, EOF)) + return (NO); + if (c == '{') /* it was "struct {" */ + return (YES); + for (sp = tok;;) { /* get next token */ + *sp++ = c; + if (GETC(==, EOF)) + return (NO); + if (!intoken(c)) + break; + } + switch (c) { + case '{': /* it was "struct foo{" */ + --sp; + break; + case '\n': /* it was "struct foo\n" */ + SETLINE; + /*FALLTHROUGH*/ + default: /* probably "struct foo " */ + while (GETC(!=, EOF)) + if (!iswhite(c)) + break; + if (c != '{') { + (void)ungetc(c, inf); + return (NO); + } + } + *sp = EOS; + pfnote(tok, curline); + return (YES); +} + +/* + * skip_comment -- + * skip over comment + */ +void +skip_comment(int commenttype) +{ + int c; /* character read */ + int star; /* '*' flag */ + + for (star = 0; GETC(!=, EOF);) + switch(c) { + /* comments don't nest, nor can they be escaped. */ + case '*': + star = YES; + break; + case '/': + if (commenttype == '*' && star) + return; + break; + case '\n': + if (commenttype == '/') { + /* + * we don't really parse C, so sometimes it + * is necessary to see the newline + */ + ungetc(c, inf); + return; + } + SETLINE; + /*FALLTHROUGH*/ + default: + star = NO; + break; + } +} + +/* + * skip_string -- + * skip to the end of a string or character constant. + */ +void +skip_string(int key) +{ + int c, + skip; + + for (skip = NO; GETC(!=, EOF); ) + switch (c) { + case '\\': /* a backslash escapes anything */ + skip = !skip; /* we toggle in case it's "\\" */ + break; + case '\n': + SETLINE; + /*FALLTHROUGH*/ + default: + if (c == key && !skip) + return; + skip = NO; + } +} + +/* + * skip_key -- + * skip to next char "key" + */ +int +skip_key(int key) +{ + int c, + skip, + retval; + + for (skip = retval = NO; GETC(!=, EOF);) + switch(c) { + case '\\': /* a backslash escapes anything */ + skip = !skip; /* we toggle in case it's "\\" */ + break; + case ';': /* special case for yacc; if one */ + case '|': /* of these chars occurs, we may */ + retval = YES; /* have moved out of the rule */ + break; /* not used by C */ + case '\'': + case '"': + /* skip strings and character constants */ + skip_string(c); + break; + case '/': + /* skip comments */ + if (GETC(==, '*')) { + skip_comment(c); + break; + } else if (c == '/') { + skip_comment(c); + break; + } + (void)ungetc(c, inf); + c = '/'; + goto norm; + case '\n': + SETLINE; + /*FALLTHROUGH*/ + default: + norm: + if (c == key && !skip) + return (retval); + skip = NO; + } + return (retval); +} diff --git a/usr.bin/ctags/ctags.1 b/usr.bin/ctags/ctags.1 new file mode 100644 index 0000000..1dd72e8 --- /dev/null +++ b/usr.bin/ctags/ctags.1 @@ -0,0 +1,225 @@ +.\" $NetBSD: ctags.1,v 1.18 2017/07/03 21:34:19 wiz Exp $ +.\" +.\" Copyright (c) 1987, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)ctags.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt CTAGS 1 +.Os +.Sh NAME +.Nm ctags +.Nd create a tags file +.Sh SYNOPSIS +.Nm +.Op Fl BFadtuwvx +.Op Fl f Ar tagsfile +.Ar name ... +.Sh DESCRIPTION +.Nm +makes a tags file for +.Xr ex 1 +from the specified C, +Pascal, Fortran, +.Tn YACC , +lex, and lisp sources. +A tags file gives the locations of specified objects in a group of files. +Each line of the tags file contains the object name, the file in which it +is defined, and a search pattern for the object definition, separated by +white-space. +Using the +.Ar tags +file, +.Xr ex 1 +can quickly locate these object definitions. +Depending upon the options provided to +.Nm , +objects will consist of subroutines, typedefs, defines, structs, +enums and unions. +.Bl -tag -width Ds +.It Fl B +use backward searching patterns +.Pq Li ?...? . +.It Fl F +use forward searching patterns +.Pq Li /.../ +(the default). +.It Fl a +append to +.Ar tags +file. +.It Fl d +create tags for +.Li #defines +that don't take arguments; +.Li #defines +that take arguments are tagged automatically. +.It Fl f +Places the tag descriptions in a file called +.Ar tagsfile . +The default behaviour is to place them in a file called +.Ar tags . +.It Fl t +create tags for typedefs, structs, unions, and enums. +.It Fl u +update the specified files in the +.Ar tags +file, that is, all +references to them are deleted, and the new values are appended to the +file. +(Beware: this option is implemented in a way which is rather +slow; it is usually faster to simply rebuild the +.Ar tags +file.) +.It Fl v +An index of the form expected by +.Xr vgrind 1 +is produced on the standard output. +This listing +contains the object name, file name, and page number (assuming 64 +line pages). +Since the output will be sorted into lexicographic order, +it may be desired to run the output through +.Xr sort 1 . +Sample use: +.Bd -literal -offset indent +ctags \-v files \&| sort \-f > index +vgrind \-x index +.Ed +.It Fl w +suppress warning diagnostics. +.It Fl x +.Nm +produces a list of object +names, the line number and file name on which each is defined, as well +as the text of that line and prints this on the standard output. +This +is a simple index which can be printed out as an off-line readable +function index. +.El +.Pp +Files whose names end in +.Sq \&.c +or +.Sq \&.h +are assumed to be C +source files and are searched for C style routine and macro definitions. +Files whose names end in +.Sq \&.y +are assumed to be +.Tn YACC +source files. +Files whose names end in +.Sq \&.l +are assumed to be lisp files if their +first non-blank character is +.Sq \&; , +.Sq \&( , +or +.Sq \&[ , +otherwise, they are +treated as lex files. +Other files are first examined to see if they +contain any Pascal or Fortran routine definitions, and, if not, are +searched for C style definitions. +.Pp +The tag +.Li main +is treated specially in C programs. +The tag formed +is created by prepending +.Ar M +to the name of the file, with the +trailing +.Sq \&.c +and any leading pathname components removed. +This +makes use of +.Nm +practical in directories with more than one +program. +.Pp +Yacc and lex files each have a special tag. +.Ar Yyparse +is the start +of the second section of the yacc file, and +.Ar yylex +is the start of +the second section of the lex file. +.Sh FILES +.Bl -tag -width tags -compact +.It Pa tags +default output tags file +.El +.Sh EXIT STATUS +.Ex -std +Duplicate objects are not considered errors. +.Sh SEE ALSO +.Xr ex 1 , +.Xr vi 1 +.Sh HISTORY +The +.Nm +command appeared in +.Bx 3.0 . +.Sh BUGS +Recognition of +.Em functions , +.Em subroutines +and +.Em procedures +for +.Tn FORTRAN +and Pascal is done in a very simpleminded way. +No attempt +is made to deal with block structure; if you have two Pascal procedures +in different blocks with the same name you lose. +.Nm +doesn't +understand about Pascal types. +.Pp +The method of deciding whether to look for C, Pascal or +.Tn FORTRAN +functions is a hack. +.Pp +.Nm +relies on the input being well formed, and any syntactical +errors will completely confuse it. +It also finds some legal syntax +confusing; for example, since it doesn't understand +.Li #ifdef Ns 's +(incidentally, that's a feature, not a bug), any code with unbalanced +braces inside +.Li #ifdef Ns 's +will cause it to become somewhat disoriented. +In a similar fashion, multiple line changes within a definition will +cause it to enter the last line of the object, rather than the first, as +the searching pattern. +The last line of multiple line +.Li typedef Ns 's +will similarly be noted. diff --git a/usr.bin/ctags/ctags.c b/usr.bin/ctags/ctags.c new file mode 100644 index 0000000..5054b1b --- /dev/null +++ b/usr.bin/ctags/ctags.c @@ -0,0 +1,275 @@ +/* $NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994, 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__COPYRIGHT) && !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)ctags.c 8.4 (Berkeley) 2/7/95"; +#endif +__RCSID("$NetBSD: ctags.c,v 1.12 2008/07/21 14:19:22 lukem Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include + +#include "ctags.h" + +/* + * ctags: create a tags file + */ + +NODE *head; /* head of the sorted binary tree */ + + /* boolean "func" (see init()) */ +bool _wht[256], _etk[256], _itk[256], _btk[256], _gd[256]; + +FILE *inf; /* ioptr for current input file */ +FILE *outf; /* ioptr for tags file */ + +long lineftell; /* ftell after getc( inf ) == '\n' */ + +int lineno; /* line number of current line */ +int dflag; /* -d: non-macro defines */ +int tflag; /* -t: create tags for typedefs */ +int vflag; /* -v: vgrind style index output */ +int wflag; /* -w: suppress warnings */ +int xflag; /* -x: cxref style output */ + +char *curfile; /* current input file name */ +char searchar = '/'; /* use /.../ searches by default */ +char lbuf[LINE_MAX]; + +void init(void); +void find_entries(char *); + +int +main(int argc, char **argv) +{ + static const char *outfile = "tags"; /* output file */ + int aflag; /* -a: append to tags */ + int uflag; /* -u: update tags */ + int exit_val; /* exit value */ + int step; /* step through args */ + int ch; /* getopts char */ + char cmd[100]; /* too ugly to explain */ + + aflag = uflag = NO; + while ((ch = getopt(argc, argv, "BFadf:tuwvx")) != -1) + switch(ch) { + case 'B': + searchar = '?'; + break; + case 'F': + searchar = '/'; + break; + case 'a': + aflag++; + break; + case 'd': + dflag++; + break; + case 'f': + outfile = optarg; + break; + case 't': + tflag++; + break; + case 'u': + uflag++; + break; + case 'w': + wflag++; + break; + case 'v': + vflag++; + case 'x': + xflag++; + break; + case '?': + default: + goto usage; + } + argv += optind; + argc -= optind; + if (!argc) { +usage: (void)fprintf(stderr, + "usage: ctags [-BFadtuwvx] [-f tagsfile] file ...\n"); + exit(1); + } + + init(); + + for (exit_val = step = 0; step < argc; ++step) + if (!(inf = fopen(argv[step], "r"))) { + warn("%s", argv[step]); + exit_val = 1; + } + else { + curfile = argv[step]; + find_entries(argv[step]); + (void)fclose(inf); + } + + if (head) { + if (xflag) + put_entries(head); + else { + if (uflag) { + for (step = 0; step < argc; step++) { + (void)snprintf(cmd, sizeof(cmd), + "mv %s OTAGS; fgrep -v '\t%s\t' OTAGS >%s; rm OTAGS", + outfile, argv[step], + outfile); + system(cmd); + } + ++aflag; + } + if (!(outf = fopen(outfile, aflag ? "a" : "w"))) + err(exit_val, "%s", outfile); + put_entries(head); + (void)fclose(outf); + if (uflag) { + (void)snprintf(cmd, sizeof(cmd), + "sort -o %s %s", outfile, outfile); + system(cmd); + } + } + } + exit(exit_val); +} + +/* + * init -- + * this routine sets up the boolean psuedo-functions which work by + * setting boolean flags dependent upon the corresponding character. + * Every char which is NOT in that string is false with respect to + * the pseudo-function. Therefore, all of the array "_wht" is NO + * by default and then the elements subscripted by the chars in + * CWHITE are set to YES. Thus, "_wht" of a char is YES if it is in + * the string CWHITE, else NO. + */ +void +init(void) +{ + int i; + unsigned const char *sp; + + for (i = 0; i < 256; i++) { + _wht[i] = _etk[i] = _itk[i] = _btk[i] = NO; + _gd[i] = YES; + } +#define CWHITE " \f\t\n" + for (sp = CWHITE; *sp; sp++) /* white space chars */ + _wht[*sp] = YES; +#define CTOKEN " \t\n\"'#()[]{}=-+%*/&|^~!<>;,.:?" + for (sp = CTOKEN; *sp; sp++) /* token ending chars */ + _etk[*sp] = YES; +#define CINTOK "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz0123456789" + for (sp = CINTOK; *sp; sp++) /* valid in-token chars */ + _itk[*sp] = YES; +#define CBEGIN "ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz" + for (sp = CBEGIN; *sp; sp++) /* token starting chars */ + _btk[*sp] = YES; +#define CNOTGD ",;" + for (sp = CNOTGD; *sp; sp++) /* invalid after-function chars */ + _gd[*sp] = NO; +} + +/* + * find_entries -- + * this routine opens the specified file and calls the function + * which searches the file. + */ +void +find_entries(char *file) +{ + char *cp; + + lineno = 0; /* should be 1 ?? KB */ + if ((cp = strrchr(file, '.')) != NULL) { + if (cp[1] == 'l' && !cp[2]) { + int c; + + for (;;) { + if (GETC(==, EOF)) + return; + if (!iswhite(c)) { + rewind(inf); + break; + } + } +#define LISPCHR ";([" +/* lisp */ if (strchr(LISPCHR, c)) { + l_entries(); + return; + } +/* lex */ else { + /* + * we search all 3 parts of a lex file + * for C references. This may be wrong. + */ + toss_yysec(); + (void)strlcpy(lbuf, "%%$", sizeof(lbuf)); + pfnote("yylex", lineno); + rewind(inf); + } + } +/* yacc */ else if (cp[1] == 'y' && !cp[2]) { + /* + * we search only the 3rd part of a yacc file + * for C references. This may be wrong. + */ + toss_yysec(); + (void)strlcpy(lbuf, "%%$", sizeof(lbuf)); + pfnote("yyparse", lineno); + y_entries(); + } +/* fortran */ else if ((cp[1] != 'c' && cp[1] != 'h') && !cp[2]) { + if (PF_funcs()) + return; + rewind(inf); + } + } +/* C */ c_entries(); +} diff --git a/usr.bin/ctags/ctags.h b/usr.bin/ctags/ctags.h new file mode 100644 index 0000000..6aa2aa1 --- /dev/null +++ b/usr.bin/ctags/ctags.h @@ -0,0 +1,92 @@ +/* $NetBSD: ctags.h,v 1.9 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)ctags.h 8.3 (Berkeley) 4/2/94 + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#define bool char + +#define YES 1 +#define NO 0 +#define EOS '\0' + +#define ENDLINE 50 /* max length of pattern */ +#define MAXTOKEN 250 /* max size of single token */ + +#define SETLINE {++lineno;lineftell = ftell(inf);} +#define GETC(op,exp) ((c = getc(inf)) op (int)exp) + +#define iswhite(arg) (_wht[(unsigned)arg]) /* T if char is white */ +#define begtoken(arg) (_btk[(unsigned)arg]) /* T if char can start token */ +#define intoken(arg) (_itk[(unsigned)arg]) /* T if char can be in token */ +#define endtoken(arg) (_etk[(unsigned)arg]) /* T if char ends tokens */ +#define isgood(arg) (_gd[(unsigned)arg]) /* T if char can be after ')' */ + +typedef struct nd_st { /* sorting structure */ + struct nd_st *left, + *right; /* left and right sons */ + char *entry, /* function or type name */ + *file, /* file name */ + *pat; /* search pattern */ + int lno; /* for -x option */ + bool been_warned; /* set if noticed dup */ +} NODE; + +extern char *curfile; /* current input file name */ +extern NODE *head; /* head of the sorted binary tree */ +extern FILE *inf; /* ioptr for current input file */ +extern FILE *outf; /* ioptr for current output file */ +extern long lineftell; /* ftell after getc( inf ) == '\n' */ +extern int lineno; /* line number of current line */ +extern int dflag; /* -d: non-macro defines */ +extern int tflag; /* -t: create tags for typedefs */ +extern int vflag; /* -v: vgrind style index output */ +extern int wflag; /* -w: suppress warnings */ +extern int xflag; /* -x: cxref style output */ +extern bool _wht[], _etk[], _itk[], _btk[], _gd[]; +extern char lbuf[LINE_MAX]; +extern char *lbp; +extern char searchar; /* ex search character */ + +extern int cicmp(const char *); +extern void get_line(void); +extern void pfnote(const char *, int); +extern int skip_key(int); +extern void put_entries(NODE *); +extern void toss_yysec(void); +extern void l_entries(void); +extern void y_entries(void); +extern int PF_funcs(void); +extern void c_entries(void); +extern void skip_comment(int); diff --git a/usr.bin/ctags/fortran.c b/usr.bin/ctags/fortran.c new file mode 100644 index 0000000..2a9cefd --- /dev/null +++ b/usr.bin/ctags/fortran.c @@ -0,0 +1,174 @@ +/* $NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)fortran.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: fortran.c,v 1.11 2009/07/13 19:05:40 roy Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include "ctags.h" + +static void takeprec(void); + +char *lbp; /* line buffer pointer */ + +int +PF_funcs(void) +{ + bool pfcnt; /* pascal/fortran functions found */ + char *cp; + char tok[MAXTOKEN]; + + for (pfcnt = NO;;) { + lineftell = ftell(inf); + if (!fgets(lbuf, sizeof(lbuf), inf)) + return (pfcnt); + ++lineno; + lbp = lbuf; + if (*lbp == '%') /* Ratfor escape to fortran */ + ++lbp; + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + if (!*lbp) + continue; + switch (*lbp | ' ') { /* convert to lower-case */ + case 'c': + if (cicmp("complex") || cicmp("character")) + takeprec(); + break; + case 'd': + if (cicmp("double")) { + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + if (!*lbp) + continue; + if (cicmp("precision")) + break; + continue; + } + break; + case 'i': + if (cicmp("integer")) + takeprec(); + break; + case 'l': + if (cicmp("logical")) + takeprec(); + break; + case 'r': + if (cicmp("real")) + takeprec(); + break; + } + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + if (!*lbp) + continue; + switch (*lbp | ' ') { + case 'f': + if (cicmp("function")) + break; + continue; + case 'p': + if (cicmp("program") || cicmp("procedure")) + break; + continue; + case 's': + if (cicmp("subroutine")) + break; + default: + continue; + } + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + if (!*lbp) + continue; + for (cp = lbp + 1; *cp && intoken(*cp); ++cp) + continue; + if ((cp = lbp + 1) != NULL) + continue; + *cp = EOS; + (void)strlcpy(tok, lbp, sizeof(tok)); + get_line(); /* process line for ex(1) */ + pfnote(tok, lineno); + pfcnt = YES; + } + /*NOTREACHED*/ +} + +/* + * cicmp -- + * do case-independent strcmp + */ +int +cicmp(const char *cp) +{ + int len; + char *bp; + + for (len = 0, bp = lbp; *cp && (*cp &~ ' ') == (*bp++ &~ ' '); + ++cp, ++len) + continue; + if (!*cp) { + lbp += len; + return (YES); + } + return (NO); +} + +static void +takeprec(void) +{ + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + if (*lbp == '*') { + for (++lbp; isspace((unsigned char)*lbp); ++lbp) + continue; + if (!isdigit((unsigned char)*lbp)) + --lbp; /* force failure */ + else + while (isdigit((unsigned char)*++lbp)) + continue; + } +} diff --git a/usr.bin/ctags/lisp.c b/usr.bin/ctags/lisp.c new file mode 100644 index 0000000..d143c6c --- /dev/null +++ b/usr.bin/ctags/lisp.c @@ -0,0 +1,112 @@ +/* $NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)lisp.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: lisp.c,v 1.11 2009/07/13 19:05:40 roy Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include "ctags.h" + +/* + * lisp tag functions + * just look for (def or (DEF + */ +void +l_entries(void) +{ + int special; + char *cp; + char savedc; + char tok[MAXTOKEN]; + + for (;;) { + lineftell = ftell(inf); + if (!fgets(lbuf, sizeof(lbuf), inf)) + return; + ++lineno; + lbp = lbuf; + if (!cicmp("(def")) + continue; + special = NO; + switch(*lbp | ' ') { + case 'm': + if (cicmp("method")) + special = YES; + break; + case 'w': + if (cicmp("wrapper") || cicmp("whopper")) + special = YES; + } + for (; !isspace((unsigned char)*lbp); ++lbp) + continue; + for (; isspace((unsigned char)*lbp); ++lbp) + continue; + for (cp = lbp; *cp && *cp != '\n'; ++cp) + continue; + *cp = EOS; + if (special) { + if (!(cp = strchr(lbp, ')'))) + continue; + for (; cp >= lbp && *cp != ':'; --cp) + continue; + if (cp < lbp) + continue; + lbp = cp; + for (; *cp && *cp != ')' && *cp != ' '; ++cp) + continue; + } + else + for (cp = lbp + 1; + *cp && *cp != '(' && *cp != ' '; ++cp) + continue; + savedc = *cp; + *cp = EOS; + (void)strlcpy(tok, lbp, sizeof(tok)); + *cp = savedc; + get_line(); + pfnote(tok, lineno); + } + /*NOTREACHED*/ +} diff --git a/usr.bin/ctags/print.c b/usr.bin/ctags/print.c new file mode 100644 index 0000000..86fcd3a --- /dev/null +++ b/usr.bin/ctags/print.c @@ -0,0 +1,121 @@ +/* $NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)print.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: print.c,v 1.10 2009/07/13 19:05:40 roy Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include "ctags.h" + +/* + * get_line -- + * get the line the token of interest occurred on, + * prepare it for printing. + */ +void +get_line(void) +{ + long saveftell; + int c; + int cnt; + char *cp; + + saveftell = ftell(inf); + (void)fseek(inf, lineftell, SEEK_SET); + if (xflag) + for (cp = lbuf; GETC(!=, '\n'); *cp++ = c) + continue; + /* + * do all processing here, so we don't step through the + * line more than once; means you don't call this routine + * unless you're sure you've got a keeper. + */ + else for (cnt = 0, cp = lbuf; GETC(!=, EOF) && cnt < ENDLINE; ++cnt) { + if (c == '\\') { /* backslashes */ + if (cnt > ENDLINE - 2) + break; + *cp++ = '\\'; *cp++ = '\\'; + ++cnt; + } + else if (c == (int)searchar) { /* search character */ + if (cnt > ENDLINE - 2) + break; + *cp++ = '\\'; *cp++ = c; + ++cnt; + } + else if (c == '\n') { /* end of keep */ + *cp++ = '$'; /* can find whole line */ + break; + } + else + *cp++ = c; + } + *cp = EOS; + (void)fseek(inf, saveftell, SEEK_SET); +} + +/* + * put_entries -- + * write out the tags + */ +void +put_entries(NODE *node) +{ + + if (node->left) + put_entries(node->left); + if (vflag) + printf("%s %s %d\n", + node->entry, node->file, (node->lno + 63) / 64); + else if (xflag) + printf("%-16s%4d %-16s %s\n", + node->entry, node->lno, node->file, node->pat); + else + fprintf(outf, "%s\t%s\t%c^%s%c\n", + node->entry, node->file, searchar, node->pat, searchar); + if (node->right) + put_entries(node->right); +} diff --git a/usr.bin/ctags/test/ctags.test b/usr.bin/ctags/test/ctags.test new file mode 100644 index 0000000..658a073 --- /dev/null +++ b/usr.bin/ctags/test/ctags.test @@ -0,0 +1,69 @@ +/* $NetBSD: ctags.test,v 1.2 1995/03/26 20:14:14 glass Exp $ */ + +int bar = (1 + 5); + +FOO("here is a #define test: ) {"); +char sysent[20]; +int nsysent = sizeof (sysent) / sizeof (sysent[0]); +/* + * now is the time for a comment. + * four lines in length... + */struct struct_xtra{int list;};r4(x,y){};typedef struct{int bar;}struct_xxe; +#define FOO BAR +struct struct_three { + int list; +}; +#define SINGLE +int BAD(); +enum color {red, green, gold, brown}; +char qq[] = " quote(one,two) {int bar;} "; +typedef struct { + int bar; + struct struct_two { + int foo; + union union_3 { + struct struct_three entry; + char size[25]; + }; + struct last { + struct struct_three xentry; + char list[34]; + }; + }; +} struct_one; +#define TWOLINE ((MAXLIST + FUTURE + 15) \ + / (time_to_live ? 3 : 4)) +#if (defined(BAR)) +int bar; +#endif +#define MULTIPLE {\ + multiple(one,two); \ + lineno++; \ + callroute(one,two); \ +} +#if defined(BAR) +int bar; +#endif +union union_one { + struct struct_three s3; + char foo[25]; +}; +#define XYZ(A,B) (A + B / 2) * (3 - 26 + l_lineno) +routine1(one,two) /* comments here are fun... */ + struct { + int entry; + char bar[34]; + } *one; + char two[10]; +{ +typedef unsigned char u_char; + register struct buf *bp; + five(one,two); +} + routine2 (one,two) { puts("hello\n"); } + routine3 +(one, +two) { puts("world\n"); } +routine4(int one, char (*two)(void)) /* test ANSI arguments */ +{ +} diff --git a/usr.bin/ctags/tree.c b/usr.bin/ctags/tree.c new file mode 100644 index 0000000..93f4a41 --- /dev/null +++ b/usr.bin/ctags/tree.c @@ -0,0 +1,145 @@ +/* $NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)tree.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: tree.c,v 1.12 2006/04/05 19:38:47 dsl Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include "ctags.h" + +static void add_node(NODE *, NODE *); +static void free_tree(NODE *); + +/* + * pfnote -- + * enter a new node in the tree + */ +void +pfnote(const char *name, int ln) +{ + NODE *np; + char *fp; + char nbuf[MAXTOKEN]; + + /*NOSTRICT*/ + if (!(np = (NODE *)malloc(sizeof(NODE)))) { + warnx("too many entries to sort"); + put_entries(head); + free_tree(head); + /*NOSTRICT*/ + if (!(head = np = (NODE *)malloc(sizeof(NODE)))) + err(1, "out of space"); + } + if (!xflag && !strcmp(name, "main")) { + if (!(fp = strrchr(curfile, '/'))) + fp = curfile; + else + ++fp; + (void)snprintf(nbuf, sizeof(nbuf), "M%s", fp); + fp = strrchr(nbuf, '.'); + if (fp && !fp[2]) + *fp = EOS; + name = nbuf; + } + if (!(np->entry = strdup(name))) + err(1, "strdup"); + np->file = curfile; + np->lno = ln; + np->left = np->right = 0; + if (!(np->pat = strdup(lbuf))) + err(1, "strdup"); + if (!head) + head = np; + else + add_node(np, head); +} + +static void +add_node(NODE *node, NODE *cur_node) +{ + int dif; + + dif = strcmp(node->entry, cur_node->entry); + if (!dif) { + if (node->file == cur_node->file) { + if (!wflag) + fprintf(stderr, "Duplicate entry in file %s, line %d: %s\nSecond entry ignored\n", node->file, lineno, node->entry); + return; + } + if (!cur_node->been_warned) + if (!wflag) + fprintf(stderr, "Duplicate entry in files %s and %s: %s (Warning only)\n", node->file, cur_node->file, node->entry); + cur_node->been_warned = YES; + } + else if (dif < 0) { + if (cur_node->left) + add_node(node, cur_node->left); + else + cur_node->left = node; + } else { + if (cur_node->right) + add_node(node, cur_node->right); + else + cur_node->right = node; + } +} + +static void +free_tree(NODE *node) +{ + NODE *nnode; + + for (; node != NULL; node = nnode) { + nnode = node->left; + if (node->right) { + if (nnode == NULL) + nnode = node->right; + else + free_tree(node->right); + } + free(node); + } +} diff --git a/usr.bin/ctags/yacc.c b/usr.bin/ctags/yacc.c new file mode 100644 index 0000000..d17fa99 --- /dev/null +++ b/usr.bin/ctags/yacc.c @@ -0,0 +1,160 @@ +/* $NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if defined(__RCSID) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)yacc.c 8.3 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: yacc.c,v 1.12 2009/07/13 19:05:40 roy Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include "ctags.h" + +/* + * y_entries: + * find the yacc tags and put them in. + */ +void +y_entries(void) +{ + int c; + char *sp; + bool in_rule; + char tok[MAXTOKEN]; + + in_rule = NO; + + while (GETC(!=, EOF)) + switch (c) { + case '\n': + SETLINE; + /* FALLTHROUGH */ + case ' ': + case '\f': + case '\r': + case '\t': + break; + case '{': + if (skip_key('}')) + in_rule = NO; + break; + case '\'': + case '"': + if (skip_key(c)) + in_rule = NO; + break; + case '%': + if (GETC(==, '%')) + return; + (void)ungetc(c, inf); + break; + case '/': + if (GETC(==, '*')) + skip_comment('*'); + else + (void)ungetc(c, inf); + break; + case '|': + case ';': + in_rule = NO; + break; + default: + if (in_rule || (!isalpha(c) && c != '.' && c != '_')) + break; + sp = tok; + *sp++ = c; + while (GETC(!=, EOF) && (intoken(c) || c == '.')) + *sp++ = c; + *sp = EOS; + get_line(); /* may change before ':' */ + if (c == EOF) + return; + while (iswhite(c)) { + if (c == '\n') + SETLINE; + if (GETC(==, EOF)) + return; + } + if (c == ':') { + pfnote(tok, lineno); + in_rule = YES; + } + else + (void)ungetc(c, inf); + } +} + +/* + * toss_yysec -- + * throw away lines up to the next "\n%%\n" + */ +void +toss_yysec(void) +{ + int c; /* read character */ + int state; + + /* + * state == 0 : waiting + * state == 1 : received a newline + * state == 2 : received first % + * state == 3 : received second % + */ + lineftell = ftell(inf); + for (state = 0; GETC(!=, EOF);) + switch (c) { + case '\n': + ++lineno; + lineftell = ftell(inf); + if (state == 3) /* done! */ + return; + state = 1; /* start over */ + break; + case '%': + if (state) /* if 1 or 2 */ + ++state; /* goto 3 */ + break; + default: + state = 0; /* reset */ + break; + } +} diff --git a/usr.bin/cut/cut.1 b/usr.bin/cut/cut.1 new file mode 100644 index 0000000..a3dbf55 --- /dev/null +++ b/usr.bin/cut/cut.1 @@ -0,0 +1,126 @@ +.\" $NetBSD: cut.1,v 1.18 2012/06/20 17:53:19 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)cut.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 12, 2012 +.Dt CUT 1 +.Os +.Sh NAME +.Nm cut +.Nd select portions of each line of a file +.Sh SYNOPSIS +.Nm +.Fl b Ar list +.Op Fl n +.Op Ar +.Nm +.Fl c Ar list +.Op Ar +.Nm +.Fl f Ar list +.Op Fl d Ar string +.Op Fl s +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility selects portions of each line (as specified by +.Ar list ) +from each +.Ar file +and writes them to the +standard output. +If the +.Ar file +argument is a single dash +.Pq Sq - +or no +.Ar file +arguments were specified, lines are read from the standard input. +The items specified by +.Ar list +can be in terms of column position or in terms of fields delimited +by a special character. +Column and field numbering start from 1. +.Pp +.Ar list +is a comma or whitespace separated set of increasing numbers and/or +number ranges. +Number ranges consist of a number, a dash +.Pq Li \- , +and a second number +and select the columns or fields from the first number to the second, +inclusive. +Numbers or number ranges may be preceded by a dash, which selects all +columns or fields from 1 to the first number. +Numbers or number ranges may be followed by a dash, which selects all +columns or fields from the last number to the end of the line. +Numbers and number ranges may be repeated, overlapping, and in any order. +It is not an error to select columns or fields not present in the +input line. +.Pp +The options are as follows: +.Bl -tag -width Fl +.It Fl b Ar list +.Ar list +specifies byte positions. +.It Fl c Ar list +.Ar list +specifies character positions. +.It Fl d Ar string +Use the first character of +.Ar string +as the field delimiter character. +The default is the +.Aq TAB +character. +.It Fl f Ar list +.Ar list +specifies fields, separated by the field delimiter character. +The selected fields are output, +separated by the field delimiter character. +.It Fl n +Do not split multi-byte characters. +.It Fl s +Suppress lines with no field delimiter characters. +Unless specified, lines with no delimiters are passed through unmodified. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr paste 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/cut/cut.c b/usr.bin/cut/cut.c new file mode 100644 index 0000000..d84b46e --- /dev/null +++ b/usr.bin/cut/cut.c @@ -0,0 +1,306 @@ +/* $NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cut.c 8.3 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: cut.c,v 1.29 2014/02/03 20:22:19 wiz Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int bflag; +static int cflag; +static char dchar; +static int dflag; +static int fflag; +static int sflag; + +static void b_cut(FILE *, const char *); +static void c_cut(FILE *, const char *); +static void f_cut(FILE *, const char *); +static void get_list(char *); +static void usage(void) __dead; + +int +main(int argc, char *argv[]) +{ + FILE *fp; + void (*fcn)(FILE *, const char *); + int ch, rval; + + fcn = NULL; + (void)setlocale(LC_ALL, ""); + + dchar = '\t'; /* default delimiter is \t */ + + /* Since we don't support multi-byte characters, the -c and -b + options are equivalent, and the -n option is meaningless. */ + while ((ch = getopt(argc, argv, "b:c:d:f:sn")) != -1) + switch(ch) { + case 'b': + fcn = b_cut; + get_list(optarg); + bflag = 1; + break; + case 'c': + fcn = c_cut; + get_list(optarg); + cflag = 1; + break; + case 'd': + dchar = *optarg; + dflag = 1; + break; + case 'f': + get_list(optarg); + fcn = f_cut; + fflag = 1; + break; + case 's': + sflag = 1; + break; + case 'n': + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (fflag) { + if (cflag || bflag) + usage(); + } else if ((!cflag && !bflag) || dflag || sflag) + usage(); + else if (bflag && cflag) + usage(); + + rval = 0; + if (*argv) + for (; *argv; ++argv) { + if (strcmp(*argv, "-") == 0) + fcn(stdin, "stdin"); + else { + if ((fp = fopen(*argv, "r"))) { + fcn(fp, *argv); + (void)fclose(fp); + } else { + rval = 1; + warn("%s", *argv); + } + } + } + else + fcn(stdin, "stdin"); + return(rval); +} + +static size_t autostart, autostop, maxval; + +static char *positions = NULL; +static size_t numpositions = 0; +#define ALLOC_CHUNK _POSIX2_LINE_MAX /* malloc granularity */ + +static void +get_list(char *list) +{ + size_t setautostart, start, stop; + char *pos; + char *p; + + if (positions == NULL) { + numpositions = ALLOC_CHUNK; + positions = ecalloc(numpositions, sizeof(*positions)); + } + + /* + * set a byte in the positions array to indicate if a field or + * column is to be selected; use +1, it's 1-based, not 0-based. + * This parser is less restrictive than the Draft 9 POSIX spec. + * POSIX doesn't allow lists that aren't in increasing order or + * overlapping lists. We also handle "-3-5" although there's no + * real reason to. + */ + for (; (p = strtok(list, ", \t")) != NULL; list = NULL) { + setautostart = start = stop = 0; + if (*p == '-') { + ++p; + setautostart = 1; + } + if (isdigit((unsigned char)*p)) { + start = stop = strtol(p, &p, 10); + if (setautostart && start > autostart) + autostart = start; + } + if (*p == '-') { + if (isdigit((unsigned char)p[1])) + stop = strtol(p + 1, &p, 10); + if (*p == '-') { + ++p; + if (!autostop || autostop > stop) + autostop = stop; + } + } + if (*p) + errx(1, "[-bcf] list: illegal list value"); + if (!stop || !start) + errx(1, "[-bcf] list: values may not include zero"); + if (stop + 1 > numpositions) { + size_t newsize; + newsize = roundup(stop + 1, ALLOC_CHUNK); + positions = erealloc(positions, newsize); + (void)memset(positions + numpositions, 0, + newsize - numpositions); + numpositions = newsize; + } + if (maxval < stop) + maxval = stop; + for (pos = positions + start; start++ <= stop; pos++) + *pos = 1; + } + + /* overlapping ranges */ + if (autostop && maxval > autostop) + maxval = autostop; + + /* set autostart */ + if (autostart) + (void)memset(positions + 1, '1', autostart); +} + +static void +/*ARGSUSED*/ +f_cut(FILE *fp, const char *fname __unused) +{ + int ch, field, isdelim; + char *pos, *p, sep; + int output; + size_t len; + char *lbuf, *tbuf; + + for (sep = dchar, tbuf = NULL; (lbuf = fgetln(fp, &len)) != NULL;) { + output = 0; + if (lbuf[len - 1] != '\n') { + /* no newline at the end of the last line so add one */ + if ((tbuf = (char *)malloc(len + 1)) == NULL) + err(1, NULL); + (void)memcpy(tbuf, lbuf, len); + tbuf[len++] = '\n'; + lbuf = tbuf; + } + for (isdelim = 0, p = lbuf;; ++p) { + ch = *p; + /* this should work if newline is delimiter */ + if (ch == sep) + isdelim = 1; + if (ch == '\n') { + if (!isdelim && !sflag) + (void)fwrite(lbuf, len, 1, stdout); + break; + } + } + if (!isdelim) + continue; + + pos = positions + 1; + for (field = maxval, p = lbuf; field; --field, ++pos) { + if (*pos) { + if (output++) + (void)putchar(sep); + while ((ch = *p++) != '\n' && ch != sep) + (void)putchar(ch); + } else { + while ((ch = *p++) != '\n' && ch != sep) + continue; + } + if (ch == '\n') + break; + } + if (ch != '\n') { + if (autostop) { + if (output) + (void)putchar(sep); + for (; (ch = *p) != '\n'; ++p) + (void)putchar(ch); + } else + for (; (ch = *p) != '\n'; ++p); + } + (void)putchar('\n'); + if (tbuf) { + free(tbuf); + tbuf = NULL; + } + } + if (tbuf) + free(tbuf); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage:\tcut -b list [-n] [file ...]\n" + "\tcut -c list [file ...]\n" + "\tcut -f list [-d string] [-s] [file ...]\n"); + exit(1); +} + +/* make b_put(): */ +#define CUT_BYTE 1 +#include "x_cut.c" +#undef CUT_BYTE + +/* make c_put(): */ +#define CUT_BYTE 0 +#include "x_cut.c" +#undef CUT_BYTE diff --git a/usr.bin/cut/x_cut.c b/usr.bin/cut/x_cut.c new file mode 100644 index 0000000..006b5af --- /dev/null +++ b/usr.bin/cut/x_cut.c @@ -0,0 +1,95 @@ +/* $NetBSD: x_cut.c,v 1.2 2007/07/02 18:41:04 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam S. Moskowitz of Menlo Consulting and Marciano Pitargue. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * This file is #include'd twice from cut.c, to generate both + * single- and multibyte versions of the same code. + * + * In cut.c #define: + * CUT_BYTE=0 to define b_cut (singlebyte), and + * CUT_BYTE=1 to define c_cut (multibyte). + * + */ + +#if (CUT_BYTE == 1) +# define CUT_FN b_cut +# define CUT_CH_T int +# define CUT_GETC getc +# define CUT_EOF EOF +# define CUT_PUTCHAR putchar +#else +# define CUT_FN c_cut +# define CUT_CH_T wint_t +# define CUT_GETC getwc +# define CUT_EOF WEOF +# define CUT_PUTCHAR putwchar +#endif + + +/* ARGSUSED */ +void +CUT_FN(FILE *fp, const char *fname __unused) +{ + CUT_CH_T ch; + int col; + char *pos; + + ch = 0; + for (;;) { + pos = positions + 1; + for (col = maxval; col; --col) { + if ((ch = CUT_GETC(fp)) == EOF) + return; + if (ch == '\n') + break; + if (*pos++) + (void)CUT_PUTCHAR(ch); + } + if (ch != '\n') { + if (autostop) + while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n') + (void)CUT_PUTCHAR(ch); + else + while ((ch = CUT_GETC(fp)) != CUT_EOF && ch != '\n'); + } + (void)CUT_PUTCHAR('\n'); + } +} + +#undef CUT_FN +#undef CUT_CH_T +#undef CUT_GETC +#undef CUT_EOF +#undef CUT_PUTCHAR + diff --git a/usr.bin/dirname/dirname.c b/usr.bin/dirname/dirname.c new file mode 100644 index 0000000..bc9d0ae --- /dev/null +++ b/usr.bin/dirname/dirname.c @@ -0,0 +1,86 @@ +/* $NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)dirname.c 8.4 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: dirname.c,v 1.12 2011/09/16 15:39:25 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + char *p; + int ch; + + setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (argc != 1) + usage(); + + if ((p = dirname(*argv)) == NULL) + err(1, "%s", *argv); + (void)printf("%s\n", p); + exit(0); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: dirname path\n"); + exit(1); +} diff --git a/usr.bin/du/du.1 b/usr.bin/du/du.1 new file mode 100644 index 0000000..14bb436 --- /dev/null +++ b/usr.bin/du/du.1 @@ -0,0 +1,168 @@ +.\" $NetBSD: du.1,v 1.23 2012/05/14 21:14:14 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)du.1 8.2 (Berkeley) 4/1/94 +.\" +.Dd May 14, 2012 +.Dt DU 1 +.Os +.Sh NAME +.Nm du +.Nd display disk usage statistics +.Sh SYNOPSIS +.Nm +.Op Fl H | Fl L | Fl P +.Op Fl a | Fl d Ar depth | Fl s +.Op Fl cghikmnrx +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility displays the file system usage for each file argument +and for each directory in the file hierarchy rooted in each directory +argument. +If no file is specified, the block usage of the hierarchy rooted in +the current directory is displayed. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl H +Symbolic links on the command line are followed. +(Symbolic links encountered in the tree traversal are not followed.) +.It Fl L +All symbolic links are followed. +.It Fl P +No symbolic links are followed. +.It Fl a +Display an entry for each file in the file hierarchy. +.It Fl c +Display the grand total after all the arguments have been processed. +.It Fl d +Display an entry files and directories +.Ar depth +directories deep. +.It Fl g +If the +.Fl g +flag is specified, the number displayed is the number of gigabyte +(1024*1024*1024 bytes) blocks. +.It Fl h +If the +.Fl h +flag is specified, the numbers will be displayed in "human-readable" +format. +Use unit suffixes: B (Byte), K (Kilobyte), M (Megabyte), G (Gigabyte), +T (Terabyte) and P (Petabyte). +.It Fl i +Output inode usage instead of blocks. +All "human-readable" options are ignored. +.It Fl k +By default, +.Nm +displays the number of blocks as returned by the +.Xr stat 2 +system call, i.e. 512-byte blocks. +If the +.Fl k +flag is specified, the number displayed is the number of kilobyte +(1024 bytes) blocks. +Partial numbers of blocks are rounded up. +.It Fl m +If the +.Fl m +flag is specified, the number displayed is the number of megabyte +(1024*1024 bytes) blocks. +.It Fl n +Ignore files and directories with user +.Qq nodump +flag +.Pq Dv UF_NODUMP +set. +.It Fl r +Generate warning messages about directories that cannot be read. +This is the default behaviour. +.It Fl s +Display only the grand total for the specified files. +.It Fl x +Filesystem mount points are not traversed. +.El +.Pp +.Nm +counts the storage used by symbolic links and not the files they +reference unless the +.Fl H +or +.Fl L +option is specified. +If either the +.Fl H +or +.Fl L +options are specified, storage used by any symbolic links which are +followed is not counted or displayed. +The +.Fl H , +.Fl L +and +.Fl P +options override each other and the command's actions are determined +by the last one specified. +.Pp +Files having multiple hard links are counted (and displayed) a single +time per +.Nm +execution. +.Sh ENVIRONMENT +.Bl -tag -width BLOCKSIZE +.It Ev BLOCKSIZE +If the environment variable +.Ev BLOCKSIZE +is set, and the +.Fl g , +.Fl h , +.Fl k , +and +.Fl m +options are not specified, the block counts will be displayed in units of that +size block. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr df 1 , +.Xr chflags 2 , +.Xr fts 3 , +.Xr getbsize 3 , +.Xr symlink 7 , +.Xr quot 8 +.Sh HISTORY +A +.Nm +command appeared in +.At v6 . diff --git a/usr.bin/du/du.c b/usr.bin/du/du.c new file mode 100644 index 0000000..5b13232 --- /dev/null +++ b/usr.bin/du/du.c @@ -0,0 +1,362 @@ +/* $NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Newcomb. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)du.c 8.5 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: du.c,v 1.36 2012/03/11 11:23:20 shattered Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Count inodes or file size */ +#define COUNT (iflag ? 1 : p->fts_statp->st_blocks) + +static int linkchk(dev_t, ino_t); +static void prstat(const char *, int64_t); +__dead static void usage(void); + +static int hflag, iflag; +static long blocksize; + +int +main(int argc, char *argv[]) +{ + FTS *fts; + FTSENT *p; + int64_t totalblocks; + int ftsoptions, listfiles; + int depth; + int Hflag, Lflag, aflag, ch, cflag, dflag, gkmflag, nflag, rval, sflag; + const char *noargv[2]; + + Hflag = Lflag = aflag = cflag = dflag = gkmflag = nflag = sflag = 0; + totalblocks = 0; + ftsoptions = FTS_PHYSICAL; + depth = INT_MAX; + while ((ch = getopt(argc, argv, "HLPacd:ghikmnrsx")) != -1) + switch (ch) { + case 'H': + Hflag = 1; + Lflag = 0; + break; + case 'L': + Lflag = 1; + Hflag = 0; + break; + case 'P': + Hflag = Lflag = 0; + break; + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + break; + case 'd': + dflag = 1; + depth = atoi(optarg); + if (depth < 0 || depth > SHRT_MAX) { + warnx("invalid argument to option d: %s", + optarg); + usage(); + } + break; + case 'g': + blocksize = 1024 * 1024 * 1024; + gkmflag = 1; + break; + case 'h': + hflag = 1; + break; + case 'i': + iflag = 1; + break; + case 'k': + blocksize = 1024; + gkmflag = 1; + break; + case 'm': + blocksize = 1024 * 1024; + gkmflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'r': + break; + case 's': + sflag = 1; + break; + case 'x': + ftsoptions |= FTS_XDEV; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + /* + * XXX + * Because of the way that fts(3) works, logical walks will not count + * the blocks actually used by symbolic links. We rationalize this by + * noting that users computing logical sizes are likely to do logical + * copies, so not counting the links is correct. The real reason is + * that we'd have to re-implement the kernel's symbolic link traversing + * algorithm to get this right. If, for example, you have relative + * symbolic links referencing other relative symbolic links, it gets + * very nasty, very fast. The bottom line is that it's documented in + * the man page, so it's a feature. + */ + if (Hflag) + ftsoptions |= FTS_COMFOLLOW; + if (Lflag) { + ftsoptions &= ~FTS_PHYSICAL; + ftsoptions |= FTS_LOGICAL; + } + + listfiles = 0; + if (aflag) { + if (sflag || dflag) + usage(); + listfiles = 1; + } else if (sflag) { + if (dflag) + usage(); + depth = 0; + } + + if (!*argv) { + noargv[0] = "."; + noargv[1] = NULL; + argv = __UNCONST(noargv); + } + + if (!gkmflag) + (void)getbsize(NULL, &blocksize); + blocksize /= 512; + + if ((fts = fts_open(argv, ftsoptions, NULL)) == NULL) + err(1, "fts_open `%s'", *argv); + + for (rval = 0; (p = fts_read(fts)) != NULL;) { + if (nflag) { + switch (p->fts_info) { + case FTS_NS: + case FTS_SLNONE: + /* nothing */ + break; + default: + if (p->fts_statp->st_flags & UF_NODUMP) { + fts_set(fts, p, FTS_SKIP); + continue; + } + } + } + switch (p->fts_info) { + case FTS_D: /* Ignore. */ + break; + case FTS_DP: + p->fts_parent->fts_number += + p->fts_number += COUNT; + if (cflag) + totalblocks += COUNT; + /* + * If listing each directory, or not listing files + * or directories and this is post-order of the + * root of a traversal, display the total. + */ + if (p->fts_level <= depth + || (!listfiles && !p->fts_level)) + prstat(p->fts_path, p->fts_number); + break; + case FTS_DC: /* Ignore. */ + break; + case FTS_DNR: /* Warn, continue. */ + case FTS_ERR: + case FTS_NS: + warnx("%s: %s", p->fts_path, strerror(p->fts_errno)); + rval = 1; + break; + default: + if (p->fts_statp->st_nlink > 1 && + linkchk(p->fts_statp->st_dev, p->fts_statp->st_ino)) + break; + /* + * If listing each file, or a non-directory file was + * the root of a traversal, display the total. + */ + if (listfiles || !p->fts_level) + prstat(p->fts_path, COUNT); + p->fts_parent->fts_number += COUNT; + if (cflag) + totalblocks += COUNT; + } + } + if (errno) + err(1, "fts_read"); + if (cflag) + prstat("total", totalblocks); + exit(rval); +} + +static void +prstat(const char *fname, int64_t blocks) +{ + if (iflag) { + (void)printf("%" PRId64 "\t%s\n", blocks, fname); + return; + } + + if (hflag) { + char buf[5]; + int64_t sz = blocks * 512; + + humanize_number(buf, sizeof(buf), sz, "", HN_AUTOSCALE, + HN_B | HN_NOSPACE | HN_DECIMAL); + + (void)printf("%s\t%s\n", buf, fname); + } else + (void)printf("%" PRId64 "\t%s\n", + howmany(blocks, (int64_t)blocksize), + fname); +} + +static int +linkchk(dev_t dev, ino_t ino) +{ + static struct entry { + dev_t dev; + ino_t ino; + } *htable; + static int htshift; /* log(allocated size) */ + static int htmask; /* allocated size - 1 */ + static int htused; /* 2*number of insertions */ + static int sawzero; /* Whether zero is in table or not */ + int h, h2; + uint64_t tmp; + /* this constant is (1<<64)/((1+sqrt(5))/2) + * aka (word size)/(golden ratio) + */ + const uint64_t HTCONST = 11400714819323198485ULL; + const int HTBITS = CHAR_BIT * sizeof(tmp); + + /* Never store zero in hashtable */ + if (dev == 0 && ino == 0) { + h = sawzero; + sawzero = 1; + return h; + } + + /* Extend hash table if necessary, keep load under 0.5 */ + if (htused<<1 >= htmask) { + struct entry *ohtable; + + if (!htable) + htshift = 10; /* starting hashtable size */ + else + htshift++; /* exponential hashtable growth */ + + htmask = (1 << htshift) - 1; + htused = 0; + + ohtable = htable; + htable = calloc(htmask+1, sizeof(*htable)); + if (!htable) + err(1, "calloc"); + + /* populate newly allocated hashtable */ + if (ohtable) { + int i; + for (i = 0; i <= htmask>>1; i++) + if (ohtable[i].ino || ohtable[i].dev) + linkchk(ohtable[i].dev, ohtable[i].ino); + free(ohtable); + } + } + + /* multiplicative hashing */ + tmp = dev; + tmp <<= HTBITS>>1; + tmp |= ino; + tmp *= HTCONST; + h = tmp >> (HTBITS - htshift); + h2 = 1 | ( tmp >> (HTBITS - (htshift<<1) - 1)); /* must be odd */ + + /* open address hashtable search with double hash probing */ + while (htable[h].ino || htable[h].dev) { + if ((htable[h].ino == ino) && (htable[h].dev == dev)) + return 1; + h = (h + h2) & htmask; + } + + /* Insert the current entry into hashtable */ + htable[h].dev = dev; + htable[h].ino = ino; + htused++; + return 0; +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "usage: du [-H | -L | -P] [-a | -d depth | -s] [-cghikmnrx] [file ...]\n"); + exit(1); +} diff --git a/usr.bin/env/env.1 b/usr.bin/env/env.1 new file mode 100644 index 0000000..d4597bd --- /dev/null +++ b/usr.bin/env/env.1 @@ -0,0 +1,122 @@ +.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)printenv.1 6.7 (Berkeley) 7/28/91 +.\" $NetBSD: env.1,v 1.12 2007/06/08 18:20:42 wiz Exp $ +.\" +.Dd June 8, 2007 +.Dt ENV 1 +.Os +.Sh NAME +.Nm env +.Nd set and print environment +.Sh SYNOPSIS +.Nm +.Op Fl i +.Op Ar name=value ... +.Oo +.Ar utility +.Op argument ... +.Oc +.Sh DESCRIPTION +.Nm +executes +.Ar utility +after modifying the environment as +specified on the command line. +The option +.Ar name=value +specifies +an environmental variable, +.Ar name , +with a value of +.Ar value . +The option +.Sq Fl i +causes +.Nm +to completely ignore the environment +it inherits. +.Pp +If no +.Ar utility +is specified, +.Nm +prints out the names and values +of the variables in the environment, with one +.Ar name=value +pair per line. +.Sh EXIT STATUS +.Nm +exits with one of the following values: +.Bl -tag -width Ds +.It 0 +.Ar utility +was invoked and completed successfully. +In this case the exit code is returned by the utility itself, not +.Nm . +If no utility was specified, then +.Nm +completed successfully and returned the exit code itself. +.It 1 +An invalid command line option was passed to +.Nm . +.It 1\-125 +.Ar utility +was invoked, but failed in some way; +see its manual page for more information. +In this case the exit code is returned by the utility itself, not +.Nm . +.It 126 +.Ar utility +was found, but could not be invoked. +.It 127 +.Ar utility +could not be found. +.El +.Sh COMPATIBILITY +The historic +.Fl +option has been deprecated but is still supported in this implementation. +.Sh SEE ALSO +.Xr execvp 3 , +.Xr environ 7 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.Sh BUGS +.Nm +doesn't handle commands with equal +.Pq Dq = +signs in their +names, for obvious reasons. diff --git a/usr.bin/env/env.c b/usr.bin/env/env.c new file mode 100644 index 0000000..bf2430d --- /dev/null +++ b/usr.bin/env/env.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +/*static char sccsid[] = "@(#)env.c 8.3 (Berkeley) 4/2/94";*/ +__RCSID("$NetBSD: env.c,v 1.20 2010/11/16 02:53:49 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) __attribute__((__noreturn__)); + +extern char **environ; + +int +main(int argc, char **argv) +{ + char **ep; + char *cleanenv[1]; + int ch; + + setprogname(*argv); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "-i")) != -1) + switch((char)ch) { + case '-': /* obsolete */ + case 'i': + environ = cleanenv; + cleanenv[0] = NULL; + break; + case '?': + default: + usage(); + } + + for (argv += optind; *argv && strchr(*argv, '=') != NULL; ++argv) + (void)putenv(*argv); + + if (*argv) { + /* return 127 if the command to be run could not be found; 126 + if the command was found but could not be invoked */ + + (void)execvp(*argv, argv); + err((errno == ENOENT) ? 127 : 126, "%s", *argv); + /* NOTREACHED */ + } + + for (ep = environ; *ep; ep++) + (void)printf("%s\n", *ep); + + exit(0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-i] [name=value ...] [command]\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/expand/expand.1 b/usr.bin/expand/expand.1 new file mode 100644 index 0000000..5f36025 --- /dev/null +++ b/usr.bin/expand/expand.1 @@ -0,0 +1,87 @@ +.\" $NetBSD: expand.1,v 1.12 2012/05/12 14:52:57 reed Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)expand.1 8.1 (Berkeley) 6/9/93 +.\" +.Dd December 20, 2008 +.Dt EXPAND 1 +.Os +.Sh NAME +.Nm expand , +.Nm unexpand +.Nd expand tabs to spaces, and vice versa +.Sh SYNOPSIS +.Nm +.Op Fl t Ar tabstop +.Op Fl t Ar tab1,tab2,...,tabn +.Op Ar +.Nm unexpand +.Op Fl a +.Op Fl t Ar tab1,tab2,...,tabn +.Op Ar +.Sh DESCRIPTION +.Nm +processes the named files or the standard input writing +the standard output with tabs changed into blanks. +Backspace characters are preserved into the output and decrement +the column count for tab calculations. +.Nm +is useful for pre-processing character files +(before sorting, looking at specific columns, etc.) that +contain tabs. +.Pp +If a single +.Ar tabstop +argument is given, then tabs are set +.Ar tabstop +spaces apart instead of the default 8. +If multiple tabstops are given then the tabs are set at those +specific columns. +.Pp +.Nm unexpand +puts tabs back into the data from the standard input or the named +files and writes the result on the standard output. +.Pp +Option (with +.Nm unexpand +only): +.Bl -tag -width flag +.It Fl a +By default, only leading blanks and tabs +are reconverted to maximal strings of tabs. +If the +.Fl a +option is given, then tabs are inserted whenever they would compress the +resultant file by replacing two or more characters. +.El +.Sh HISTORY +The +.Nm +command appeared in +.Bx 1 . diff --git a/usr.bin/expand/expand.c b/usr.bin/expand/expand.c new file mode 100644 index 0000000..c69fda1 --- /dev/null +++ b/usr.bin/expand/expand.c @@ -0,0 +1,182 @@ +/* $NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $ */ + +/* + * Copyright (c) 1980, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.1 (Berkeley) 6/9/93"; +#endif +__RCSID("$NetBSD: expand.c,v 1.14 2016/09/05 00:40:28 sevan Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +/* + * expand - expand tabs to equivalent spaces + */ +size_t nstops; +size_t tabstops[100]; + +static void getstops(const char *); +static void usage(void) __dead; + +int +main(int argc, char *argv[]) +{ + int c; + size_t n, column; + + setprogname(argv[0]); + + /* handle obsolete syntax */ + while (argc > 1 && + argv[1][0] == '-' && isdigit((unsigned char)argv[1][1])) { + getstops(&argv[1][1]); + argc--; argv++; + } + + while ((c = getopt (argc, argv, "t:")) != -1) { + switch (c) { + case 't': + getstops(optarg); + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + do { + if (argc > 0) { + if (freopen(argv[0], "r", stdin) == NULL) + err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); + argc--, argv++; + } + column = 0; + while ((c = getchar()) != EOF) { + switch (c) { + case '\t': + if (nstops == 0) { + do { + putchar(' '); + column++; + } while (column & 07); + continue; + } + if (nstops == 1) { + do { + putchar(' '); + column++; + } while (((column - 1) % tabstops[0]) + != (tabstops[0] - 1)); + continue; + } + for (n = 0; n < nstops; n++) + if (tabstops[n] > column) + break; + if (n == nstops) { + putchar(' '); + column++; + continue; + } + while (column < tabstops[n]) { + putchar(' '); + column++; + } + continue; + + case '\b': + if (column) + column--; + putchar('\b'); + continue; + + default: + putchar(c); + column++; + continue; + + case '\n': + putchar(c); + column = 0; + continue; + } + } + } while (argc > 0); + return EXIT_SUCCESS; +} + +static void +getstops(const char *spec) +{ + int i; + const char *cp = spec; + + nstops = 0; + for (;;) { + i = 0; + while (*cp >= '0' && *cp <= '9') + i = i * 10 + *cp++ - '0'; + if (i <= 0 || i > 256) + errx(EXIT_FAILURE, "Too large tab stop spec `%d'", i); + if (nstops > 0 && (size_t)i <= tabstops[nstops-1]) + errx(EXIT_FAILURE, "Out of order tabstop spec `%d'", i); + if (nstops == sizeof(tabstops) / sizeof(tabstops[0]) - 1) + errx(EXIT_FAILURE, "Too many tabstops"); + tabstops[nstops++] = i; + if (*cp == '\0') + break; + if (*cp != ',' && *cp != ' ') + errx(EXIT_FAILURE, "Illegal tab stop spec `%s'", spec); + cp++; + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "Usage: %s [-t tablist] [file ...]\n", + getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/false/false.1 b/usr.bin/false/false.1 new file mode 100644 index 0000000..175b67a --- /dev/null +++ b/usr.bin/false/false.1 @@ -0,0 +1,56 @@ +.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $ +.\" +.\" Copyright (c) 1983, 1990 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)false.1 6.6 (Berkeley) 7/24/91 +.\" $NetBSD: false.1,v 1.8 2003/08/07 11:13:40 agc Exp $ +.\" +.Dd July 24, 1991 +.Dt FALSE 1 +.Os +.Sh NAME +.Nm false +.Nd return false value +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility always exits with a nonzero exit code. +.Sh SEE ALSO +.Xr csh 1 , +.Xr sh 1 , +.Xr true 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/false/false.sh b/usr.bin/false/false.sh new file mode 100644 index 0000000..2526efb --- /dev/null +++ b/usr.bin/false/false.sh @@ -0,0 +1,2 @@ +#! /bin/sh +exit 1 diff --git a/usr.bin/find/extern.h b/usr.bin/find/extern.h new file mode 100644 index 0000000..5c94035 --- /dev/null +++ b/usr.bin/find/extern.h @@ -0,0 +1,102 @@ +/* $NetBSD: extern.h,v 1.29 2016/06/13 00:04:40 pgoyette Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)extern.h 8.3 (Berkeley) 4/16/94 + */ + +#include + +void brace_subst(char *, char **, char *, int *); +PLAN *find_create(char ***); +int find_execute(PLAN *, char **); +PLAN *find_formplan(char **); +int find_traverse(PLAN *, int (*)(PLAN *, void *), void *); +int f_expr(PLAN *, FTSENT *); +PLAN *not_squish(PLAN *); +PLAN *or_squish(PLAN *); +PLAN *paren_squish(PLAN *); +int plan_cleanup(PLAN *, void *); +void printlong(char *, char *, struct stat *); +int queryuser(char **); +void show_path(int); + +PLAN *c_amin(char ***, int, char *); +PLAN *c_anewer(char ***, int, char *); +PLAN *c_asince(char ***, int, char *); +PLAN *c_atime(char ***, int, char *); +PLAN *c_cmin(char ***, int, char *); +PLAN *c_cnewer(char ***, int, char *); +PLAN *c_csince(char ***, int, char *); +PLAN *c_ctime(char ***, int, char *); +PLAN *c_delete(char ***, int, char *); +PLAN *c_depth(char ***, int, char *); +PLAN *c_empty(char ***, int, char *); +PLAN *c_exec(char ***, int, char *); +PLAN *c_execdir(char ***, int, char *); +PLAN *c_exit(char ***, int, char *); +PLAN *c_false(char ***, int, char *); +PLAN *c_flags(char ***, int, char *); +PLAN *c_follow(char ***, int, char *); +PLAN *c_fprint(char ***, int, char *); +PLAN *c_fstype(char ***, int, char *); +PLAN *c_group(char ***, int, char *); +PLAN *c_iname(char ***, int, char *); +PLAN *c_inum(char ***, int, char *); +PLAN *c_iregex(char ***, int, char *); +PLAN *c_links(char ***, int, char *); +PLAN *c_ls(char ***, int, char *); +PLAN *c_maxdepth(char ***, int, char *); +PLAN *c_mindepth(char ***, int, char *); +PLAN *c_mmin(char ***, int, char *); +PLAN *c_mtime(char ***, int, char *); +PLAN *c_name(char ***, int, char *); +PLAN *c_newer(char ***, int, char *); +PLAN *c_nogroup(char ***, int, char *); +PLAN *c_nouser(char ***, int, char *); +PLAN *c_path(char ***, int, char *); +PLAN *c_perm(char ***, int, char *); +PLAN *c_print(char ***, int, char *); +PLAN *c_print0(char ***, int, char *); +PLAN *c_printx(char ***, int, char *); +PLAN *c_prune(char ***, int, char *); +PLAN *c_regex(char ***, int, char *); +PLAN *c_since(char ***, int, char *); +PLAN *c_size(char ***, int, char *); +PLAN *c_type(char ***, int, char *); +PLAN *c_user(char ***, int, char *); +PLAN *c_xdev(char ***, int, char *); +PLAN *c_openparen(char ***, int, char *); +PLAN *c_closeparen(char ***, int, char *); +PLAN *c_not(char ***, int, char *); +PLAN *c_or(char ***, int, char *); +PLAN *c_null(char ***, int, char *); + +extern int ftsoptions, isdeprecated, isdepth, isoutput, issort, isxargs, + regcomp_flags; diff --git a/usr.bin/find/find.1 b/usr.bin/find/find.1 new file mode 100644 index 0000000..60ce94a --- /dev/null +++ b/usr.bin/find/find.1 @@ -0,0 +1,974 @@ +.\" $NetBSD: find.1,v 1.89 2017/07/03 21:34:57 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)find.1 8.7 (Berkeley) 5/9/95 +.\" +.Dd June 13, 2016 +.Dt FIND 1 +.Os +.Sh NAME +.Nm find +.Nd walk a file hierarchy +.Sh SYNOPSIS +.Nm +.Op Fl H | Fl L | Fl P +.Op Fl dEhsXx +.Ar file +.Op Ar file ... +.Op Ar expression +.Nm +.Op Fl H | Fl L | Fl P +.Op Fl dEhsXx +.Fl f Ar file +.Op Ar file ... +.Op Ar expression +.Sh DESCRIPTION +.Nm +recursively descends the directory tree for each +.Ar file +listed, evaluating an +.Ar expression +(composed of the +.Dq primaries +and +.Dq operands +listed below) in terms +of each file in the tree. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl H +Causes the file information and file type (see +.Xr stat 2 ) +returned for each symbolic link encountered on the command line to be +those of the file referenced by the link, not the link itself. +If the referenced file does not exist, the file information and type will +be for the link itself. +File information of all symbolic links not on the command line is that +of the link itself. +.It Fl L +Causes the file information and file type (see +.Xr stat 2 ) +returned for each symbolic link to be those of the file referenced by the +link, not the link itself. +If the referenced file does not exist, the file information and type will +be for the link itself. +.It Fl P +Causes the file information and file type (see +.Xr stat 2 ) +returned for each symbolic link to be those of the link itself. +.It Fl d +Causes +.Nm +to perform a depth-first traversal, i.e., directories +are visited in post-order, and all entries in a directory will be acted +on before the directory itself. +By default, +.Nm +visits directories in pre-order, i.e., before their contents. +Note, the default is +.Em not +a breadth-first traversal. +.It Fl E +Causes +.Ar regexp +arguments to primaries to be interpreted as extended regular +expressions (see +.Xr re_format 7 ) . +.It Fl f +Specifies a file hierarchy for +.Nm +to traverse. +File hierarchies may also be specified as the operands immediately +following the options. +.It Fl h +Causes the file information and file type (see +.Xr stat 2 ) +returned for each symbolic link to be those of the file referenced by the +link, not the link itself. +If the referenced file does not exist, the file information and type will +be for the link itself. +.It Fl s +Causes the entries of each directory to be sorted in +lexicographical order. +Note that the sorting is done only inside of each directory; +files in different directories are not sorted. +Therefore, +.Sq Li a/b +appears before +.Sq Li a.b , +which is different from +.Dq Li "find ... \&| sort" +order. +.It Fl X +Modifies the output to permit +.Nm +to be safely used in conjunction with +.Xr xargs 1 . +If a file name contains any of the delimiting characters used by +.Xr xargs 1 , +a diagnostic message is displayed on standard error, and the file +is skipped. +The delimiting characters include single +.Pq Dq \&' +and double +.Pq Dq \&" +quotes, backslash +.Pq Dq \e , +space, tab, and newline characters. +Alternatively, the +.Ic -print0 +or +.Ic -printx +primaries can be used to format the output in a way that +.Xr xargs 1 +can accept. +.It Fl x +Restricts the search to the file system containing the +directory specified. +Does not list mount points to other file systems. +.El +.Sh PRIMARIES +All primaries which take a numeric argument of +.Ar n +allow the number to be preceded by a plus sign +.Pq Dq \&+ +or a minus sign +.Pq Dq \- . +A preceding plus sign means +.Dq more than Ar n , +a preceding minus sign means +.Dq less than Ar n , +and neither means +.Dq exactly Ar n . +(The argument specified for the +.Ic -user +and +.Ic -group +primaries +are similarly treated if the value is numeric and does not correspond to a +valid user or group name.) +.Pp +For primaries which take a +.Ar timestamp +argument, the argument must be valid input to +.Xr parsedate 3 . +If the argument contains multiple words, enclose the argument in quotes. +.Pp +.Bl -tag -width Ds -compact +.It Ic -amin Ar n +True if the difference between the file last access time and the time +.Nm +was started, rounded up to the next full minute, is +.Ar n +minutes. +.Pp +.It Ic -anewer Ar file +True if the current file has a more recent last access time than +.Ar file . +.Pp +.It Ic -asince Ar timestamp +True if the file last access time is greater than the specified +.Ar timestamp . +.Pp +.It Ic -atime Ar n +True if the difference between the file last access time and the time +.Nm +was started, rounded up to the next full 24-hour period, is +.Ar n +24-hour periods. +.Pp +.It Ic -cmin Ar n +True if the difference between the time of last change of file status +information and the time +.Nm +was started, rounded up to the next full minute, is +.Ar n +minutes. +.Pp +.It Ic -cnewer Ar file +True if the current file has a more recent last change time than +.Ar file . +.Pp +.It Ic -csince Ar timestamp +True if the file last status change time is greater than the specified +.Ar timestamp . +.Pp +.It Ic -ctime Ar n +True if the difference between the time of last change of file status +information and the time +.Nm +was started, rounded up to the next full 24-hour period, is +.Ar n +24-hour periods. +.Pp +.It Ic -delete +Delete found files, symbolic links, and directories. +Always returns true. +This executes from the current working directory as +.Nm +recurses down the tree. +To avoid deleting unexpected files, it will ignore any filenames that +.Xr fts 3 +returns that contain a +.Dq / +.Xr ( fts 3 +should not return such pathnames). +Depth-first traversal processing is implied by this option. +This primary can also be invoked as +.Ic -rm . +.Pp +.It Ic -empty +True if the current file or directory is empty. +.Pp +.It Ic -exec Ar utility Oo argument ... Oc Ic \&; +.It Ic -exec Ar utility Oo argument ... Oc Ic {} Ic \&+ +Execute the specified +.Ar utility +with the specified arguments. +.Pp +The list of arguments for +.Ar utility +is terminated by a lone semicolon +.Dq Ic \&; +or plus +.Dq Ic \&+ +character as a separate parameter. +The command specified by +.Ar utility +will be executed with its current working directory being the directory +from which +.Nm +was executed. +.Pp +If the list of arguments is terminated by a semicolon +.Pq Dq Ic \&; , +then +.Ar utility +is invoked once per pathname. +If +the string +.Dq Ic {} +appears one or more times in the utility name or arguments, +then it is replaced by the pathname of the current file +(but it need not appear, in which case the pathname +will not be passed to +.Ar utility ) . +The semicolon-terminated form of the +.Ic -exec +primary returns true if and only if +.Ar utility +exits with a zero exit status. +Note that the semicolon will have to be escaped on the shell command line +in order to be passed as a parameter. +.Pp +If the list of arguments is terminated by a plus sign +.Pq Dq Ic \&+ , +then the pathnames for which the primary is evaluated are aggregated +into sets, and +.Ar utility +will be invoked once per set, similar to +.Xr xargs 1 . +In this case the string +.Dq Ic {} +must appear, and must appear as the last item in the argument list, +just before the +.Dq Ic \&+ +parameter, and is replaced by the pathnames of the current set of files. +Each set is limited to no more than 5,000 pathnames, +and is also limited such that the total number of bytes in the argument +list does not exceed +.Dv ARG_MAX . +The plus-terminated form of the +.Ic -exec +primary always returns true. +If the plus-terminated form of the +.Ic -exec +primary results in any invocation of +.Ar utility +exiting with non-zero exit status, then +.Nm +will eventually exit with non-zero status as well, +but this does not cause +.Nm +to exit early. +.Pp +.It Ic -execdir Ar utility Oo argument ... Oc Ic \&; +The +.Ic -execdir +primary is similar to the semicolon-terminated +.Pq Dq Ic \&; +variant of the +.Ic -exec +primary, with the exception that +.Ar utility +will be executed from the directory that holds +the current file. +Only the base filename is substituted for the string +.Dq Ic {} . +Set aggregation +.Pq Do Ic \&+ Dc termination +is not supported. +.Pp +.It Ic -exit Op Ar status +This primary causes +.Nm +to stop traversing the file system and exit immediately, +with the specified numeric exit status. +If the +.Ar status +value is not specified, then +.Nm +will exit with status zero. +Note that any preceding primaries will be evaluated and acted upon +before exiting. +.Pp +.It Ic -false +This primary always evaluates to false. +This can be used following a primary that caused the +expression to be true to make the expression to be false. +This can be useful after using a +.Ic -fprint +primary so it can continue to the next expression (using an +.Cm -or +operator, for example). +.Pp +.It Ic -flags Oo Fl Oc Ns Ar flags +If +.Ar flags +are preceded by a dash +.Pq Dq Ic \- , +this primary evaluates to true +if at least all of the bits in +.Ar flags +are set in the file's flags bits. +If +.Ar flags +are not preceded by a dash, this primary evaluates to true if +the bits in +.Ar flags +exactly match the file's flags bits. +If +.Ar flags +is +.Dq none , +files with no flags bits set are matched. +(See +.Xr chflags 1 +for more information about file flags.) +.Pp +.It Ic -follow +Follow symbolic links. +.Pp +.It Ic -fprint Ar filename +This primary always evaluates to true. +This creates +.Ar filename +or overwrites the file if it already exists. +The file is created at startup. +It writes the pathname of the current file to this file, followed +by a newline character. +The file will be empty if no files are matched. +.Pp +.It Ic -fstype Ar type +True if the file is contained in a file system of type +.Ar type . +The +.Xr sysctl 8 +command can be used to find out the types of file systems +that are available on the system: +.Bd -literal -offset indent +sysctl vfs.generic.fstypes +.Ed +.Pp +In addition, there are two pseudo-types, +.Dq local +and +.Dq rdonly . +The former matches any file system physically mounted on the system where +the +.Nm +is being executed, and the latter matches any file system which is +mounted read-only. +.Pp +.It Ic -group Ar gname +True if the file belongs to the group +.Ar gname . +If +.Ar gname +is numeric and there is no such group name, then +.Ar gname +is treated as a group id (and considered a numeric argument). +.Pp +.It Ic -iname Ar pattern +True if the last component of the pathname being examined matches +.Ar pattern +in a case-insensitive manner. +Special shell pattern matching characters +.Po +.Dq \&[ , +.Dq \&] , +.Dq \&* , +and +.Dq \&? +.Pc +may be used as part of +.Ar pattern . +These characters may be matched explicitly by escaping them with a +backslash +.Pq Dq \e . +.Pp +.It Ic -inum Ar n +True if the file has inode number +.Ar n . +.Pp +.It Ic -iregex Ar regexp +True if the path name of the current file matches the case-insensitive +basic regular expression +.Pq see Xr re_format 7 +.Ar regexp . +This is a match on the whole path, not a search for the regular expression +within the path. +.Pp +.It Ic -links Ar n +True if the file has +.Ar n +links. +.Pp +.It Ic -rm +This primary is an alias for +.Ic -delete . +.Pp +.It Ic -ls +This primary always evaluates to true. +The following information for the current file is written to standard output: +its inode number, size in 512-byte blocks, file permissions, number of hard +links, owner, group, size in bytes, last modification time, and pathname. +If the file is a block or character special file, the major and minor numbers +will be displayed instead of the size in bytes. +If the file is a symbolic link, the pathname of the linked-to file will be +displayed preceded by +.Dq -> . +The format is identical to that produced by +.Dq ls -dgils . +.Pp +.It Ic -maxdepth Ar depth +True if the current search depth is less than or equal to what is specified in +.Ar depth . +.Pp +.It Ic -mindepth Ar depth +True if the current search depth is at least what is specified in +.Ar depth . +.Pp +.It Ic -mmin Ar n +True if the difference between the file last modification time and the time +.Nm +was started, rounded up to the next full minute, is +.Ar n +minutes. +.Pp +.It Ic -mtime Ar n +True if the difference between the file last modification time and the time +.Nm +was started, rounded up to the next full 24-hour period, is +.Ar n +24-hour periods. +.Pp +.It Ic -ok Ar utility Oo argument ... Oc Ic \&; +The +.Ic -ok +primary is similar to the semicolon-terminated +.Pq Dq \&; +variant of the +.Ic -exec +primary, with the exception that +.Nm +requests user affirmation for the execution of +.Ar utility +by printing +a message to the terminal and reading a response. +If the response is other than +.Dq y , +the command is not executed and the +.Ic -ok +primary evaluates to false. +Set aggregation +.Pq Do \&+ Dc termination +is not supported. +.Pp +.It Ic -name Ar pattern +True if the last component of the pathname being examined matches +.Ar pattern . +Special shell pattern matching characters +.Po +.Dq \&[ , +.Dq \&] , +.Dq \&* , +and +.Dq \&? +.Pc +may be used as part of +.Ar pattern . +These characters may be matched explicitly by escaping them with a +backslash +.Pq Dq \e . +.Pp +.It Ic -newer Ar file +True if the current file has a more recent last modification time than +.Ar file . +.Pp +.It Ic -newerXY Ar reference +For compatibility with Gnu findutils. +.Bl -column -offset indent ".Sy findutils" ".Sy equivalent" +.It Sy findutils Ta Sy find +.It Sy option Ta Sy equivalent +.It -neweraa Ta -anewer +.It -newerat Ta -asince +.It -newercc Ta -cnewer +.It -newerct Ta -csince +.It -newermm Ta -newer +.It -newermt Ta -since +.El +.Pp +Other option variants from findutils are not implemented. +.Pp +.It Ic -nouser +True if the file belongs to an unknown user. +.Pp +.It Ic -nogroup +True if the file belongs to an unknown group. +.Pp +.It Ic -path Ar pattern +True if the pathname being examined matches +.Ar pattern . +Special shell pattern matching characters +.Po +.Dq \&[ , +.Dq \&] , +.Dq \&* , +and +.Dq \&? +.Pc +may be used as part of +.Ar pattern . +These characters may be matched explicitly by escaping them with a +backslash +.Pq Dq \e . +Slashes +.Pq Dq / +are treated as normal characters and do not have to be +matched explicitly. +.Pp +.It Ic -perm Oo Fl Oc Ns Ar mode +The +.Ar mode +may be either symbolic (see +.Xr chmod 1 ) +or an octal number. +If the mode is symbolic, a starting value of zero is assumed and the +mode sets or clears permissions without regard to the process' file mode +creation mask. +If the mode is octal, only bits 07777 +.Pf ( Dv S_ISUID +| +.Dv S_ISGID +| +.Dv S_ISTXT +| +.Dv S_IRWXU +| +.Dv S_IRWXG +| +.Dv S_IRWXO ) +of the file's mode bits participate +in the comparison. +If the mode is preceded by a dash +.Pq Dq Ic \- , +this primary evaluates to true +if at least all of the bits in the mode are set in the file's mode bits. +If the mode is not preceded by a dash, this primary evaluates to true if +the bits in the mode exactly match the file's mode bits. +Note, the first character of a symbolic mode may not be a dash +.Pq Dq Ic \- . +.Pp +.It Ic -print +This primary always evaluates to true. +It prints the pathname of the current file to standard output, followed +by a newline character. +If none of +.Ic -delete , +.Ic -exec , +.Ic -execdir , +.Ic -exit , +.Ic -fprint , +.Ic -ls , +.Ic -ok , +.Ic -print0 , +.Ic -printx , +nor +.Ic -rm +is specified, the given expression shall be effectively replaced by +.Cm \&( Ns Ar given\& expression Ns Cm \&) +.Ic -print . +.Pp +.It Ic -print0 +This primary always evaluates to true. +It prints the pathname of the current file to standard output, followed +by a NUL character. +.Pp +.It Ic -printx +This primary always evaluates to true. +It prints the pathname of the current file to standard output, +with each space, tab, newline, backslash, dollar sign, and single, +double, or back quotation mark prefixed by a backslash, so the output of +.Nm +can safely be used as input to +.Xr xargs 1 . +.Pp +.It Ic -prune +This primary always evaluates to true. +It causes +.Nm +to not descend into the current file. +Note, the +.Ic -prune +primary has no effect if the +.Fl d +option was specified. +.Pp +.It Ic -regex Ar regexp +True if the path name of the current file matches the case-sensitive +basic regular expression +.Pq see Xr re_format 7 +.Ar regexp . +This is a match on the whole path, not a search for the regular expression +within the path. +.Pp +.It Ic -since Ar timestamp +True if the file last modification time is more recent than +.Ar timestamp . +.Pp +.It Ic -size Ar n Ns Op Cm c +True if the file's size, rounded up, in 512-byte blocks is +.Ar n . +If +.Ar n +is followed by a +.Dq Ic c , +then the primary is true if the file's size is +.Ar n +bytes. +.Pp +.It Ic -type Ar t +True if the file is of the specified type. +Possible file types are as follows: +.Pp +.Bl -tag -width flag -offset indent -compact +.It Cm b +block special +.It Cm c +character special +.It Cm d +directory +.It Cm f +regular file +.It Cm l +symbolic link +.It Cm p +FIFO +.It Cm s +socket +.It Cm W +whiteout +.It Cm w +whiteout +.El +.Pp +.It Ic -user Ar username +True if the file belongs to the user +.Ar username . +If +.Ar username +is numeric and there is no such user on the system, then +.Ar username +is treated as a user id (and considered a numeric argument). +.Pp +.It Ic -xdev +This primary always evaluates to true. +It causes find not to descend past directories that have a different +device ID +.Va ( st_dev , +see +.Xr stat 2 +S5.6.2 [POSIX.1]). +.El +.Sh OPERATORS +The primaries may be combined using the following operators. +The operators are listed in order of decreasing precedence. +.Bl -tag -width (expression) +.It Cm \&( Ar expression Cm \&) +This evaluates to true if the parenthesized expression evaluates to +true. +.It Cm \&! Ar expression +This is the unary +.Tn NOT +operator. +It evaluates to true if the expression is false. +.It Ar expression Cm -and Ar expression +.It Ar expression expression +The +.Cm -and +operator is the logical +.Tn AND +operator. +As it is implied by the juxtaposition of two expressions it does not +have to be specified. +The expression evaluates to true if both expressions are true. +The second expression is not evaluated if the first expression is false. +.It Ar expression Cm -or Ar expression +The +.Cm -or +operator is the logical +.Tn OR +operator. +The expression evaluates to true if either the first or the second expression +is true. +The second expression is not evaluated if the first expression is true. +.El +.Pp +All operands and primaries must be separate arguments to +.Nm . +Primaries which themselves take arguments expect each argument +to be a separate argument to +.Nm . +.Sh EXIT STATUS +The +.Nm +utility normally exits 0 on success, and exits with 1 under certain +internal error conditions. +If any invocations of +.Dq Ic -exec Ar ... Ic \&+ +primaries return non-zero exit-status, then +.Nm +will do so as well. +.Sh EXAMPLES +The following examples are shown as given to the shell: +.Bl -tag -width findx +.It Li "find / \e! -name \*q*.c\*q \-print" +Print out a list of all the files whose names do not end in +.Dq \&.c . +.It Li "find / \-newer ttt \-user wnj \-print" +Print out a list of all the files owned by user +.Dq wnj +that are newer than the file +.Dq ttt . +.It Li "find . \-type f \-mmin \-30 \-print \-or \-mindepth 1 \-prune" +Print out a list of all the files in the current directory that are +newer than 30 minutes. +.It Li "find . \-type f \-atime +10 \-mindepth 2 \-print" +Print out a list of all the files in any sub-directories that have not +been accessed in the past ten days. +.It Li "find . \-mtime +90 \-exec rm \-i {} + \-or \-mindepth 1 \-prune" +Interactively remove all of the files in the current directory that have +not been modified in 90 days. +.It Li "find . \-type f \-mtime +90 \-ok mv {} {}.old \e;" +Interactively rename all of the files in the current directory and all +sub-directories that have not been modified in 90 days. +.It Li "find / \e! \e( \-newer ttt \-user wnj \e) \-print" +Print out a list of all the files which are not both newer than +.Dq ttt +and owned by +.Dq wnj . +.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-print" +Print out a list of all the files that are either owned by +.Dq wnj +or that are newer than +.Dq ttt . +.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-exit 1" +Return immediately with a value of 1 if any files are found that are either +owned by +.Dq wnj +or that are newer than +.Dq ttt , +but do not print them. +.It Li "find / \e( \-newer ttt \-or \-user wnj \e) \-ls \-exit 1" +Same as above, but list the first file matching the criteria before exiting +with a value of 1. +.It Li "find . \-type f \-exec sh \-c 'file=\*[q]$1\*[q]; ...;' - {} \;" +Perform an arbitrarily complex shell command for every file. +.El +.Sh SEE ALSO +.Xr chflags 1 , +.Xr chmod 1 , +.Xr locate 1 , +.Xr xargs 1 , +.Xr stat 2 , +.Xr fts 3 , +.Xr getgrent 3 , +.Xr getpwent 3 , +.Xr strmode 3 , +.Xr re_format 7 , +.Xr symlink 7 , +.Xr sysctl 8 +.Sh STANDARDS +The +.Nm +utility syntax is a superset of the syntax specified by the +.St -p1003.2 +standard. +.Pp +The options and the +.Ic -amin , +.Ic -anewer , +.Ic -asince , +.Ic -cmin , +.Ic -cnewer , +.Ic -csince , +.Ic -delete , +.Ic -empty , +.Ic -execdir , +.Ic -follow , +.Ic -fstype , +.Ic -iname , +.Ic -inum , +.Ic -iregex , +.Ic -links , +.Ic -ls , +.Ic -maxdepth , +.Ic -mindepth , +.Ic -mmin , +.Ic -path , +.Ic -print0 , +.Ic -printx , +.Ic -regex , +.Ic -rm , +and +.Ic -since +primaries are extensions to +.St -p1003.2 . +.Pp +Historically, the +.Fl d , +.Fl h , +and +.Fl x +options were implemented using the primaries +.Dq Ic -depth , +.Dq Ic -follow , +and +.Dq Ic -xdev . +These primaries always evaluated to true, and always +took effect when the +.Ar expression +was parsed, before the file system traversal began. +As a result, some legal expressions could be confusing. +For example, in the expression +.Dq Ic -print Ic -or Ic -depth , +.Ic -print +always evaluates to true, so the standard meaning of +.Ic -or +implies that +.Ic -depth +would never be evaluated, but that is not what happens; +in fact, +.Ic -depth +takes effect immediately, without testing whether +.Ic -print +returns true or false. +.Pp +Historically, the operator +.Dq Ic -or +was implemented as +.Dq Ic -o , +and the operator +.Dq Ic -and +was implemented as +.Dq Ic -a . +.Pp +Historic implementations of the +.Dq Ic -exec +and +.Dq Ic -ok +primaries did not replace the string +.Dq Ic {} +in the utility name or the +utility arguments if it did not appear as a separate argument. +This version replaces it no matter where in the utility name or arguments +it appears. +.Pp +Support for +.Dq Ic -exec Ar ... Ic \&+ +is consistent with +.Em IEEE PASC Interpretation 1003.2 #210 , +though the feature originated in +.Tn SVR4 . +.Pp +The +.Ic -delete +primary does not interact well with other options that cause the file system +tree traversal options to be changed. +.Sh HISTORY +A much simpler +.Nm +command appeared in First Edition AT&T Unix. +The syntax had become similar to the present version by +the time of the Fifth Edition. +.Sh BUGS +The special characters used by +.Nm +are also special characters to many shell programs. +In particular, the characters +.Dq \&* , +.Dq \&[ , +.Dq \&] , +.Dq \&? , +.Dq \&( , +.Dq \&) , +.Dq \&! , +.Dq \e , +and +.Dq \&; +may have to be escaped from the shell. +.Pp +As there is no delimiter separating options and file names or file +names and the +.Ar expression , +it is difficult to specify files named +.Dq -xdev +or +.Dq \&! . +These problems are handled by the +.Fl f +option and the +.Xr getopt 3 +.Dq -- +construct. diff --git a/usr.bin/find/find.c b/usr.bin/find/find.c new file mode 100644 index 0000000..473244b --- /dev/null +++ b/usr.bin/find/find.c @@ -0,0 +1,306 @@ +/* $NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)find.c 8.5 (Berkeley) 8/5/94"; +#else +__RCSID("$NetBSD: find.c,v 1.30 2016/06/13 00:04:40 pgoyette Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "find.h" + +static int ftscompare(const FTSENT **, const FTSENT **); + +/* + * find_formplan -- + * process the command line and create a "plan" corresponding to the + * command arguments. + */ +PLAN * +find_formplan(char **argv) +{ + PLAN *plan, *tail, *new; + + /* + * for each argument in the command line, determine what kind of node + * it is, create the appropriate node type and add the new plan node + * to the end of the existing plan. The resulting plan is a linked + * list of plan nodes. For example, the string: + * + * % find . -name foo -newer bar -print + * + * results in the plan: + * + * [-name foo]--> [-newer bar]--> [-print] + * + * in this diagram, `[-name foo]' represents the plan node generated + * by c_name() with an argument of foo and `-->' represents the + * plan->next pointer. + */ + for (plan = tail = NULL; *argv;) { + if (!(new = find_create(&argv))) + continue; + if (plan == NULL) + tail = plan = new; + else { + tail->next = new; + tail = new; + } + } + + /* + * if the user didn't specify one of -print, -ok, -fprint, -exec, or + * -exit, then -print is assumed so we bracket the current expression + * with parens, if necessary, and add a -print node on the end. + */ + if (!isoutput) { + if (plan == NULL) { + new = c_print(NULL, 0, NULL); + tail = plan = new; + } else { + new = c_openparen(NULL, 0, NULL); + new->next = plan; + plan = new; + new = c_closeparen(NULL, 0, NULL); + tail->next = new; + tail = new; + new = c_print(NULL, 0, NULL); + tail->next = new; + tail = new; + } + } + + /* + * the command line has been completely processed into a search plan + * except for the (, ), !, and -o operators. Rearrange the plan so + * that the portions of the plan which are affected by the operators + * are moved into operator nodes themselves. For example: + * + * [!]--> [-name foo]--> [-print] + * + * becomes + * + * [! [-name foo] ]--> [-print] + * + * and + * + * [(]--> [-depth]--> [-name foo]--> [)]--> [-print] + * + * becomes + * + * [expr [-depth]-->[-name foo] ]--> [-print] + * + * operators are handled in order of precedence. + */ + + plan = paren_squish(plan); /* ()'s */ + plan = not_squish(plan); /* !'s */ + plan = or_squish(plan); /* -o's */ + return (plan); +} + +static int +ftscompare(const FTSENT **e1, const FTSENT **e2) +{ + + return (strcoll((*e1)->fts_name, (*e2)->fts_name)); +} + +static sigset_t ss; +static bool notty; + +static __inline void +sig_init(void) +{ + struct sigaction sa; + notty = !(isatty(STDIN_FILENO) || isatty(STDOUT_FILENO) || + isatty(STDERR_FILENO)); + if (notty) + return; + sigemptyset(&ss); + sigaddset(&ss, SIGINFO); /* block SIGINFO */ + + memset(&sa, 0, sizeof(sa)); + sa.sa_flags = SA_RESTART; + sa.sa_handler = show_path; + (void)sigaction(SIGINFO, &sa, NULL); + +} + +static __inline void +sig_lock(sigset_t *s) +{ + if (notty) + return; + sigprocmask(SIG_BLOCK, &ss, s); +} + +static __inline void +sig_unlock(const sigset_t *s) +{ + if (notty) + return; + sigprocmask(SIG_SETMASK, s, NULL); +} + +FTS *tree; /* pointer to top of FTS hierarchy */ +FTSENT *g_entry; /* shared with SIGINFO handler */ + +/* + * find_execute -- + * take a search plan and an array of search paths and executes the plan + * over all FTSENT's returned for the given search paths. + */ +int +find_execute(PLAN *plan, char **paths) +{ + PLAN *p; + int r, rval, cval; + sigset_t s; + + cval = 1; + + if (!(tree = fts_open(paths, ftsoptions, issort ? ftscompare : NULL))) + err(1, "ftsopen"); + + sig_init(); + sig_lock(&s); + for (rval = 0; cval && (g_entry = fts_read(tree)) != NULL;) { + switch (g_entry->fts_info) { + case FTS_D: + if (isdepth) + continue; + break; + case FTS_DP: + if (!isdepth) + continue; + break; + case FTS_DNR: + case FTS_ERR: + case FTS_NS: + sig_unlock(&s); + (void)fflush(stdout); + warnx("%s: %s", + g_entry->fts_path, strerror(g_entry->fts_errno)); + rval = 1; + sig_lock(&s); + continue; + } +#define BADCH " \t\n\\'\"" + if (isxargs && strpbrk(g_entry->fts_path, BADCH)) { + sig_unlock(&s); + (void)fflush(stdout); + warnx("%s: illegal path", g_entry->fts_path); + rval = 1; + sig_lock(&s); + continue; + } + + /* + * Call all the functions in the execution plan until one is + * false or all have been executed. This is where we do all + * the work specified by the user on the command line. + */ + sig_unlock(&s); + for (p = plan; p && (p->eval)(p, g_entry); p = p->next) + if (p->type == N_EXIT) { + rval = p->exit_val; + cval = 0; + } + sig_lock(&s); + } + + sig_unlock(&s); + if (g_entry == NULL && errno) + err(1, "fts_read"); + (void)fts_close(tree); + + /* + * Cleanup any plans with leftover state. + * Keep the last non-zero return value. + */ + if ((r = find_traverse(plan, plan_cleanup, NULL)) != 0) + rval = r; + + return (rval); +} + +/* + * find_traverse -- + * traverse the plan tree and execute func() on all plans. This + * does not evaluate each plan's eval() function; it is intended + * for operations that must run on all plans, such as state + * cleanup. + * + * If any func() returns non-zero, then so will find_traverse(). + */ +int +find_traverse(PLAN *plan, int (*func)(PLAN *, void *), void *arg) +{ + PLAN *p; + int r, rval; + + rval = 0; + for (p = plan; p; p = p->next) { + if ((r = func(p, arg)) != 0) + rval = r; + if (p->type == N_EXPR || p->type == N_OR) { + if (p->p_data[0]) + if ((r = find_traverse(p->p_data[0], + func, arg)) != 0) + rval = r; + if (p->p_data[1]) + if ((r = find_traverse(p->p_data[1], + func, arg)) != 0) + rval = r; + } + } + return rval; +} diff --git a/usr.bin/find/find.h b/usr.bin/find/find.h new file mode 100644 index 0000000..912e60d --- /dev/null +++ b/usr.bin/find/find.h @@ -0,0 +1,138 @@ +/* $NetBSD: find.h,v 1.26 2016/06/13 00:04:40 pgoyette Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)find.h 8.1 (Berkeley) 6/6/93 + */ + +#include +#include + +/* node type */ +enum ntype { + N_AND = 1, /* must start > 0 */ + N_AMIN, N_ANEWER, N_ASINCE, N_ATIME, N_CLOSEPAREN, N_CMIN, N_CNEWER, + N_CSINCE, N_CTIME, N_DEPTH, N_EMPTY, N_EXEC, N_EXECDIR, N_EXIT, + N_EXPR, N_FALSE, N_FLAGS, N_FOLLOW, N_FPRINT, N_FSTYPE, N_GROUP, + N_INAME, N_INUM, N_IREGEX, N_LINKS, N_LS, N_MINDEPTH, N_MAXDEPTH, + N_MMIN, N_MTIME, N_NAME, N_NEWER, N_NOGROUP, N_NOT, N_NOUSER, N_OK, + N_OPENPAREN, N_OR, N_PATH, N_PERM, N_PRINT, N_PRINT0, N_PRINTX, + N_PRUNE, N_REGEX, N_SINCE, N_SIZE, N_TYPE, N_USER, N_XDEV, N_DELETE +}; + +/* node definition */ +typedef struct _plandata { + struct _plandata *next; /* next node */ + int (*eval)(struct _plandata *, FTSENT *); + /* node evaluation function */ +#define F_EQUAL 1 /* [acm]time inum links size */ +#define F_LESSTHAN 2 +#define F_GREATER 3 +#define F_NEEDOK 1 /* exec ok */ +#define F_PLUSSET 2 /* -exec ... {} + */ +#define F_MTFLAG 1 /* fstype */ +#define F_MTTYPE 2 +#define F_ATLEAST 1 /* perm */ + int flags; /* private flags */ + enum ntype type; /* plan node type */ + union { + u_int32_t _f_data; /* flags */ + gid_t _g_data; /* gid */ + ino_t _i_data; /* inode */ + mode_t _m_data; /* mode mask */ + nlink_t _l_data; /* link count */ + off_t _o_data; /* file size */ + time_t _t_data; /* time value */ + struct timespec _ts_data; /* time value */ + uid_t _u_data; /* uid */ + short _mt_data; /* mount flags */ + struct _plandata *_p_data[2]; /* PLAN trees */ + struct _ex { + char **_e_argv; /* argv array */ + char **_e_orig; /* original strings */ + int *_e_len; /* allocated length */ + char **_ep_bxp; /* ptr to 1st addt'l arg */ + char *_ep_p; /* current buffer pointer */ + char *_ep_bbp; /* begin buffer pointer */ + char *_ep_ebp; /* end buffer pointer */ + int _ep_maxargs; /* max #args */ + int _ep_narg; /* # addt'l args */ + int _ep_rval; /* return value */ + } ex; + char *_a_data[2]; /* array of char pointers */ + char *_c_data; /* char pointer */ + int _exit_val; /* exit value */ + int _max_data; /* tree depth */ + int _min_data; /* tree depth */ + regex_t _regexp_data; /* compiled regexp */ + FILE *_fprint_file; /* file stream for -fprint */ + } p_un; +} PLAN; +#define a_data p_un._a_data +#define c_data p_un._c_data +#define i_data p_un._i_data +#define f_data p_un._f_data +#define g_data p_un._g_data +#define l_data p_un._l_data +#define m_data p_un._m_data +#define mt_data p_un._mt_data +#define o_data p_un._o_data +#define p_data p_un._p_data +#define t_data p_un._t_data +#define ts_data p_un._ts_data +#define u_data p_un._u_data +#define e_argv p_un.ex._e_argv +#define e_orig p_un.ex._e_orig +#define e_len p_un.ex._e_len +#define ep_p p_un.ex._ep_p +#define ep_bbp p_un.ex._ep_bbp +#define ep_ebp p_un.ex._ep_ebp +#define ep_bxp p_un.ex._ep_bxp +#define ep_cnt p_un.ex._ep_cnt +#define ep_maxargs p_un.ex._ep_maxargs +#define ep_nline p_un.ex._ep_nline +#define ep_narg p_un.ex._ep_narg +#define ep_rval p_un.ex._ep_rval +#define exit_val p_un._exit_val +#define max_data p_un._max_data +#define min_data p_un._min_data +#define regexp_data p_un._regexp_data +#define fprint_file p_un._fprint_file + +typedef struct _option { + const char *name; /* option name */ + enum ntype token; /* token type */ + PLAN *(*create)(char ***, int, char *); /* create function */ + int arg; /* function needs arg */ +} OPTION; + +#include "extern.h" diff --git a/usr.bin/find/function.c b/usr.bin/find/function.c new file mode 100644 index 0000000..02540f2 --- /dev/null +++ b/usr.bin/find/function.c @@ -0,0 +1,2025 @@ +/* $NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)function.c 8.10 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: function.c,v 1.77 2018/09/04 15:16:15 kre Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "find.h" + +#define COMPARE(a, b) { \ + switch (plan->flags) { \ + case F_EQUAL: \ + return (a == b); \ + case F_LESSTHAN: \ + return (a < b); \ + case F_GREATER: \ + return (a > b); \ + default: \ + abort(); \ + } \ +} + +static int64_t find_parsenum(PLAN *, const char *, const char *, char *); +static void run_f_exec(PLAN *); + int f_always_true(PLAN *, FTSENT *); + int f_amin(PLAN *, FTSENT *); + int f_anewer(PLAN *, FTSENT *); + int f_asince(PLAN *, FTSENT *); + int f_atime(PLAN *, FTSENT *); + int f_cmin(PLAN *, FTSENT *); + int f_cnewer(PLAN *, FTSENT *); + int f_csince(PLAN *, FTSENT *); + int f_ctime(PLAN *, FTSENT *); + int f_delete(PLAN *, FTSENT *); + int f_empty(PLAN *, FTSENT *); + int f_exec(PLAN *, FTSENT *); + int f_execdir(PLAN *, FTSENT *); + int f_false(PLAN *, FTSENT *); + int f_flags(PLAN *, FTSENT *); + int f_fprint(PLAN *, FTSENT *); + int f_fstype(PLAN *, FTSENT *); + int f_group(PLAN *, FTSENT *); + int f_iname(PLAN *, FTSENT *); + int f_inum(PLAN *, FTSENT *); + int f_links(PLAN *, FTSENT *); + int f_ls(PLAN *, FTSENT *); + int f_mindepth(PLAN *, FTSENT *); + int f_maxdepth(PLAN *, FTSENT *); + int f_mmin(PLAN *, FTSENT *); + int f_mtime(PLAN *, FTSENT *); + int f_name(PLAN *, FTSENT *); + int f_newer(PLAN *, FTSENT *); +/* + * Unimplemented Gnu findutils options + * + int f_newerBB(PLAN *, FTSENT *); + int f_newerBa(PLAN *, FTSENT *); + int f_newerBc(PLAN *, FTSENT *); + int f_newerBm(PLAN *, FTSENT *); + int f_newerBt(PLAN *, FTSENT *); + int f_neweraB(PLAN *, FTSENT *); + int f_newerac(PLAN *, FTSENT *); + int f_neweram(PLAN *, FTSENT *); + int f_newerca(PLAN *, FTSENT *); + int f_newercm(PLAN *, FTSENT *); + int f_newercB(PLAN *, FTSENT *); + int f_newermB(PLAN *, FTSENT *); + int f_newerma(PLAN *, FTSENT *); + int f_newermc(PLAN *, FTSENT *); + * + */ + int f_nogroup(PLAN *, FTSENT *); + int f_nouser(PLAN *, FTSENT *); + int f_path(PLAN *, FTSENT *); + int f_perm(PLAN *, FTSENT *); + int f_print(PLAN *, FTSENT *); + int f_print0(PLAN *, FTSENT *); + int f_printx(PLAN *, FTSENT *); + int f_prune(PLAN *, FTSENT *); + int f_regex(PLAN *, FTSENT *); + int f_since(PLAN *, FTSENT *); + int f_size(PLAN *, FTSENT *); + int f_type(PLAN *, FTSENT *); + int f_user(PLAN *, FTSENT *); + int f_not(PLAN *, FTSENT *); + int f_or(PLAN *, FTSENT *); +static PLAN *c_regex_common(char ***, int, enum ntype, bool); +static PLAN *palloc(enum ntype, int (*)(PLAN *, FTSENT *)); + +extern int dotfd; +extern FTS *tree; +extern time_t now; + +/* + * find_parsenum -- + * Parse a string of the form [+-]# and return the value. + */ +static int64_t +find_parsenum(PLAN *plan, const char *option, const char *vp, char *endch) +{ + int64_t value; + const char *str; + char *endchar; /* Pointer to character ending conversion. */ + + /* Determine comparison from leading + or -. */ + str = vp; + switch (*str) { + case '+': + ++str; + plan->flags = F_GREATER; + break; + case '-': + ++str; + plan->flags = F_LESSTHAN; + break; + default: + plan->flags = F_EQUAL; + break; + } + + /* + * Convert the string with strtol(). Note, if strtol() returns zero + * and endchar points to the beginning of the string we know we have + * a syntax error. + */ + value = strtoq(str, &endchar, 10); + if (value == 0 && endchar == str) + errx(1, "%s: %s: illegal numeric value", option, vp); + if (endchar[0] && (endch == NULL || endchar[0] != *endch)) + errx(1, "%s: %s: illegal trailing character", option, vp); + if (endch) + *endch = endchar[0]; + return (value); +} + +/* + * find_parsedate -- + * + * Validate the timestamp argument or report an error + */ +static time_t +find_parsedate(PLAN *plan, const char *option, const char *vp) +{ + time_t timestamp; + + errno = 0; + timestamp = parsedate(vp, NULL, NULL); + if (timestamp == -1 && errno != 0) + errx(1, "%s: %s: invalid timestamp value", option, vp); + return timestamp; +} + +/* + * The value of n for the inode times (atime, ctime, and mtime) is a range, + * i.e. n matches from (n - 1) to n 24 hour periods. This interacts with + * -n, such that "-mtime -1" would be less than 0 days, which isn't what the + * user wanted. Correct so that -1 is "less than 1". + */ +#define TIME_CORRECT(p, ttype) \ + if ((p)->type == ttype && (p)->flags == F_LESSTHAN) \ + ++((p)->t_data); + +/* + * -amin n functions -- + * + * True if the difference between the file access time and the + * current time is n 1 minute periods. + */ +int +f_amin(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_atime + + SECSPERMIN - 1) / SECSPERMIN, plan->t_data); +} + +PLAN * +c_amin(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_AMIN, f_amin); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_AMIN); + return (new); +} + +/* + * -anewer file functions -- + * + * True if the current file has been accessed more recently + * than the access time of the file named by the pathname + * file. + */ +int +f_anewer(PLAN *plan, FTSENT *entry) +{ + + return timespeccmp(&entry->fts_statp->st_atim, &plan->ts_data, >); +} + +PLAN * +c_anewer(char ***argvp, int isok, char *opt) +{ + char *filename = **argvp; + PLAN *new; + struct stat sb; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + if (stat(filename, &sb)) + err(1, "%s: %s", opt, filename); + new = palloc(N_ANEWER, f_anewer); + new->ts_data = sb.st_atim; + return (new); +} + +/* + * -asince "timestamp" functions -- + * + * True if the file access time is greater than the timestamp value + */ +int +f_asince(PLAN *plan, FTSENT *entry) +{ + COMPARE(entry->fts_statp->st_atime, plan->t_data); +} + +PLAN * +c_asince(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_ASINCE, f_asince); + new->t_data = find_parsedate(new, opt, arg); + new->flags = F_GREATER; + return (new); +} + +/* + * -atime n functions -- + * + * True if the difference between the file access time and the + * current time is n 24 hour periods. + */ +int +f_atime(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_atime + + SECSPERDAY - 1) / SECSPERDAY, plan->t_data); +} + +PLAN * +c_atime(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_ATIME, f_atime); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_ATIME); + return (new); +} + +/* + * -cmin n functions -- + * + * True if the difference between the last change of file + * status information and the current time is n 24 hour periods. + */ +int +f_cmin(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_ctime + + SECSPERMIN - 1) / SECSPERMIN, plan->t_data); +} + +PLAN * +c_cmin(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_CMIN, f_cmin); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_CMIN); + return (new); +} + +/* + * -cnewer file functions -- + * + * True if the current file has been changed more recently + * than the changed time of the file named by the pathname + * file. + */ +int +f_cnewer(PLAN *plan, FTSENT *entry) +{ + + return timespeccmp(&entry->fts_statp->st_ctim, &plan->ts_data, >); +} + +PLAN * +c_cnewer(char ***argvp, int isok, char *opt) +{ + char *filename = **argvp; + PLAN *new; + struct stat sb; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + if (stat(filename, &sb)) + err(1, "%s: %s ", opt, filename); + new = palloc(N_CNEWER, f_cnewer); + new->ts_data = sb.st_ctim; + return (new); +} + +/* + * -csince "timestamp" functions -- + * + * True if the file status change time is greater than the timestamp value + */ +int +f_csince(PLAN *plan, FTSENT *entry) +{ + COMPARE(entry->fts_statp->st_ctime, plan->t_data); +} + +PLAN * +c_csince(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_CSINCE, f_csince); + new->t_data = find_parsedate(new, opt, arg); + new->flags = F_GREATER; + return (new); +} + +/* + * -ctime n functions -- + * + * True if the difference between the last change of file + * status information and the current time is n 24 hour periods. + */ +int +f_ctime(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_ctime + + SECSPERDAY - 1) / SECSPERDAY, plan->t_data); +} + +PLAN * +c_ctime(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_CTIME, f_ctime); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_CTIME); + return (new); +} + +/* + * -delete functions -- + * + * Always true. Makes its best shot and continues on regardless. + */ +int +f_delete(PLAN *plan __unused, FTSENT *entry) +{ + /* ignore these from fts */ + if (strcmp(entry->fts_accpath, ".") == 0 || + strcmp(entry->fts_accpath, "..") == 0) + return 1; + + /* sanity check */ + if (isdepth == 0 || /* depth off */ + (ftsoptions & FTS_NOSTAT) || /* not stat()ing */ + !(ftsoptions & FTS_PHYSICAL) || /* physical off */ + (ftsoptions & FTS_LOGICAL)) /* or finally, logical on */ + errx(1, "-delete: insecure options got turned on"); + + /* Potentially unsafe - do not accept relative paths whatsoever */ + if (entry->fts_level > 0 && strchr(entry->fts_accpath, '/') != NULL) + errx(1, "-delete: %s: relative path potentially not safe", + entry->fts_accpath); + + /* Turn off user immutable bits if running as root */ + if ((entry->fts_statp->st_flags & (UF_APPEND|UF_IMMUTABLE)) && + !(entry->fts_statp->st_flags & (SF_APPEND|SF_IMMUTABLE)) && + geteuid() == 0) + chflags(entry->fts_accpath, + entry->fts_statp->st_flags &= ~(UF_APPEND|UF_IMMUTABLE)); + + /* rmdir directories, unlink everything else */ + if (S_ISDIR(entry->fts_statp->st_mode)) { + if (rmdir(entry->fts_accpath) < 0 && errno != ENOTEMPTY) + warn("-delete: rmdir(%s)", entry->fts_path); + } else { + if (unlink(entry->fts_accpath) < 0) + warn("-delete: unlink(%s)", entry->fts_path); + } + + /* "succeed" */ + return 1; +} + +PLAN * +c_delete(char ***argvp __unused, int isok, char *opt) +{ + + ftsoptions &= ~FTS_NOSTAT; /* no optimize */ + ftsoptions |= FTS_PHYSICAL; /* disable -follow */ + ftsoptions &= ~FTS_LOGICAL; /* disable -follow */ + isoutput = 1; /* possible output */ + isdepth = 1; /* -depth implied */ + + return palloc(N_DELETE, f_delete); +} + +/* + * -depth functions -- + * + * Always true, causes descent of the directory hierarchy to be done + * so that all entries in a directory are acted on before the directory + * itself. + */ +int +f_always_true(PLAN *plan, FTSENT *entry) +{ + + return (1); +} + +PLAN * +c_depth(char ***argvp, int isok, char *opt) +{ + isdepth = 1; + + return (palloc(N_DEPTH, f_always_true)); +} + +/* + * -empty functions -- + * + * True if the file or directory is empty + */ +int +f_empty(PLAN *plan, FTSENT *entry) +{ + if (S_ISREG(entry->fts_statp->st_mode) && + entry->fts_statp->st_size == 0) + return (1); + if (S_ISDIR(entry->fts_statp->st_mode)) { + struct dirent *dp; + int empty; + DIR *dir; + + empty = 1; + dir = opendir(entry->fts_accpath); + if (dir == NULL) + return (0); + for (dp = readdir(dir); dp; dp = readdir(dir)) + if (dp->d_name[0] != '.' || + (dp->d_name[1] != '\0' && + (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) { + empty = 0; + break; + } + closedir(dir); + return (empty); + } + return (0); +} + +PLAN * +c_empty(char ***argvp, int isok, char *opt) +{ + ftsoptions &= ~FTS_NOSTAT; + + return (palloc(N_EMPTY, f_empty)); +} + +/* + * [-exec | -ok] utility [arg ... ] ; functions -- + * [-exec | -ok] utility [arg ... ] {} + functions -- + * + * If the end of the primary expression is delimited by a + * semicolon: true if the executed utility returns a zero value + * as exit status. If "{}" occurs anywhere, it gets replaced by + * the current pathname. + * + * If the end of the primary expression is delimited by a plus + * sign: always true. Pathnames for which the primary is + * evaluated shall be aggregated into sets. The utility will be + * executed once per set, with "{}" replaced by the entire set of + * pathnames (as if xargs). "{}" must appear last. + * + * The current directory for the execution of utility is the same + * as the current directory when the find utility was started. + * + * The primary -ok is different in that it requests affirmation + * of the user before executing the utility. + */ +int +f_exec(PLAN *plan, FTSENT *entry) +{ + size_t cnt; + int l; + pid_t pid; + int status; + + if (plan->flags & F_PLUSSET) { + /* + * Confirm sufficient buffer space, then copy the path + * to the buffer. + */ + l = strlen(entry->fts_path); + if (plan->ep_p + l < plan->ep_ebp) { + plan->ep_bxp[plan->ep_narg++] = + strcpy(plan->ep_p, entry->fts_path); + plan->ep_p += l + 1; + + if (plan->ep_narg == plan->ep_maxargs) + run_f_exec(plan); + } else { + /* + * Without sufficient space to copy in the next + * argument, run the command to empty out the + * buffer before re-attepting the copy. + */ + run_f_exec(plan); + if ((plan->ep_p + l < plan->ep_ebp)) { + plan->ep_bxp[plan->ep_narg++] + = strcpy(plan->ep_p, entry->fts_path); + plan->ep_p += l + 1; + } else + errx(1, "insufficient space for argument"); + } + return (1); + } else { + for (cnt = 0; plan->e_argv[cnt]; ++cnt) + if (plan->e_len[cnt]) + brace_subst(plan->e_orig[cnt], + &plan->e_argv[cnt], + entry->fts_path, + &plan->e_len[cnt]); + if (plan->flags & F_NEEDOK && !queryuser(plan->e_argv)) + return (0); + + /* Don't mix output of command with find output. */ + fflush(stdout); + fflush(stderr); + + switch (pid = vfork()) { + case -1: + err(1, "vfork"); + /* NOTREACHED */ + case 0: + if (fchdir(dotfd)) { + warn("chdir"); + _exit(1); + } + execvp(plan->e_argv[0], plan->e_argv); + warn("%s", plan->e_argv[0]); + _exit(1); + } + pid = waitpid(pid, &status, 0); + return (pid != -1 && WIFEXITED(status) + && !WEXITSTATUS(status)); + } +} + +static void +run_f_exec(PLAN *plan) +{ + pid_t pid; + int rval, status; + + /* Ensure arg list is null terminated. */ + plan->ep_bxp[plan->ep_narg] = NULL; + + /* Don't mix output of command with find output. */ + fflush(stdout); + fflush(stderr); + + switch (pid = vfork()) { + case -1: + err(1, "vfork"); + /* NOTREACHED */ + case 0: + if (fchdir(dotfd)) { + warn("chdir"); + _exit(1); + } + execvp(plan->e_argv[0], plan->e_argv); + warn("%s", plan->e_argv[0]); + _exit(1); + } + + /* Clear out the argument list. */ + plan->ep_narg = 0; + plan->ep_bxp[plan->ep_narg] = NULL; + /* As well as the argument buffer. */ + plan->ep_p = plan->ep_bbp; + *plan->ep_p = '\0'; + + pid = waitpid(pid, &status, 0); + if (WIFEXITED(status)) + rval = WEXITSTATUS(status); + else + rval = -1; + + /* + * If we have a non-zero exit status, preserve it so find(1) can + * later exit with it. + */ + if (rval) + plan->ep_rval = rval; +} + +/* + * c_exec -- + * build three parallel arrays, one with pointers to the strings passed + * on the command line, one with (possibly duplicated) pointers to the + * argv array, and one with integer values that are lengths of the + * strings, but also flags meaning that the string has to be massaged. + * + * If -exec ... {} +, use only the first array, but make it large + * enough to hold 5000 args (cf. src/usr.bin/xargs/xargs.c for a + * discussion), and then allocate ARG_MAX - 4K of space for args. + */ +PLAN * +c_exec(char ***argvp, int isok, char *opt) +{ + PLAN *new; /* node returned */ + size_t cnt; + int brace, lastbrace; + char **argv, **ap, *p; + + isoutput = 1; + + new = palloc(N_EXEC, f_exec); + if (isok) + new->flags |= F_NEEDOK; + + /* + * Terminate if we encounter an arg exactly equal to ";", or an + * arg exactly equal to "+" following an arg exactly equal to + * "{}". + */ + for (ap = argv = *argvp, brace = 0;; ++ap) { + if (!*ap) + errx(1, "%s: no terminating \";\" or \"+\"", opt); + lastbrace = brace; + brace = 0; + if (strcmp(*ap, "{}") == 0) + brace = 1; + if (strcmp(*ap, ";") == 0) + break; + if (strcmp(*ap, "+") == 0 && lastbrace) { + new->flags |= F_PLUSSET; + break; + } + } + + /* + * POSIX says -ok ... {} + "need not be supported," and it does + * not make much sense anyway. + */ + if (new->flags & F_NEEDOK && new->flags & F_PLUSSET) + errx(1, "%s: terminating \"+\" not permitted.", opt); + + if (new->flags & F_PLUSSET) { + size_t c, bufsize; + + cnt = ap - *argvp - 1; /* units are words */ + new->ep_maxargs = ARG_MAX / (sizeof (char *) + 16); + if (new->ep_maxargs > 5000) + new->ep_maxargs = 5000; + new->e_argv = emalloc((cnt + new->ep_maxargs) + * sizeof(*new->e_argv)); + + /* We start stuffing arguments after the user's last one. */ + new->ep_bxp = &new->e_argv[cnt]; + new->ep_narg = 0; + + /* + * Count up the space of the user's arguments, and + * subtract that from what we allocate. + */ +#define MAXARG (ARG_MAX - 4 * 1024) + for (argv = *argvp, c = 0, cnt = 0; + argv < ap; + ++argv, ++cnt) { + c += strlen(*argv) + 1; + if (c >= MAXARG) + errx(1, "Arguments too long"); + new->e_argv[cnt] = *argv; + } + if (c + new->ep_maxargs * sizeof (char *) >= MAXARG) + errx(1, "Arguments too long"); + bufsize = MAXARG - c - new->ep_maxargs * sizeof (char *); + + /* + * Allocate, and then initialize current, base, and + * end pointers. + */ + new->ep_p = new->ep_bbp = emalloc(bufsize + 1); + new->ep_ebp = new->ep_bbp + bufsize - 1; + new->ep_rval = 0; + } else { /* !F_PLUSSET */ + cnt = ap - *argvp + 1; + new->e_argv = emalloc(cnt * sizeof(*new->e_argv)); + new->e_orig = emalloc(cnt * sizeof(*new->e_orig)); + new->e_len = emalloc(cnt * sizeof(*new->e_len)); + + for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { + new->e_orig[cnt] = *argv; + for (p = *argv; *p; ++p) + if (p[0] == '{' && p[1] == '}') { + new->e_argv[cnt] = + emalloc(MAXPATHLEN); + new->e_len[cnt] = MAXPATHLEN; + break; + } + if (!*p) { + new->e_argv[cnt] = *argv; + new->e_len[cnt] = 0; + } + } + new->e_orig[cnt] = NULL; + } + + new->e_argv[cnt] = NULL; + *argvp = argv + 1; + return (new); +} + +/* + * -execdir utility [arg ... ] ; functions -- + * + * True if the executed utility returns a zero value as exit status. + * The end of the primary expression is delimited by a semicolon. If + * "{}" occurs anywhere, it gets replaced by the unqualified pathname. + * The current directory for the execution of utility is the same as + * the directory where the file lives. + */ +int +f_execdir(PLAN *plan, FTSENT *entry) +{ + size_t cnt; + pid_t pid; + int status; + char *file; + + /* XXX - if file/dir ends in '/' this will not work -- can it? */ + if ((file = strrchr(entry->fts_path, '/'))) + file++; + else + file = entry->fts_path; + + for (cnt = 0; plan->e_argv[cnt]; ++cnt) + if (plan->e_len[cnt]) + brace_subst(plan->e_orig[cnt], &plan->e_argv[cnt], + file, &plan->e_len[cnt]); + + /* don't mix output of command with find output */ + fflush(stdout); + fflush(stderr); + + switch (pid = vfork()) { + case -1: + err(1, "fork"); + /* NOTREACHED */ + case 0: + execvp(plan->e_argv[0], plan->e_argv); + warn("%s", plan->e_argv[0]); + _exit(1); + } + pid = waitpid(pid, &status, 0); + return (pid != -1 && WIFEXITED(status) && !WEXITSTATUS(status)); +} + +/* + * c_execdir -- + * build three parallel arrays, one with pointers to the strings passed + * on the command line, one with (possibly duplicated) pointers to the + * argv array, and one with integer values that are lengths of the + * strings, but also flags meaning that the string has to be massaged. + */ +PLAN * +c_execdir(char ***argvp, int isok, char *opt) +{ + PLAN *new; /* node returned */ + size_t cnt; + char **argv, **ap, *p; + + ftsoptions &= ~FTS_NOSTAT; + isoutput = 1; + + new = palloc(N_EXECDIR, f_execdir); + + for (ap = argv = *argvp;; ++ap) { + if (!*ap) + errx(1, "%s: no terminating \";\"", opt); + if (**ap == ';') + break; + } + + cnt = ap - *argvp + 1; + new->e_argv = emalloc(cnt * sizeof(*new->e_argv)); + new->e_orig = emalloc(cnt * sizeof(*new->e_orig)); + new->e_len = emalloc(cnt * sizeof(*new->e_len)); + + for (argv = *argvp, cnt = 0; argv < ap; ++argv, ++cnt) { + new->e_orig[cnt] = *argv; + for (p = *argv; *p; ++p) + if (p[0] == '{' && p[1] == '}') { + new->e_argv[cnt] = emalloc(MAXPATHLEN); + new->e_len[cnt] = MAXPATHLEN; + break; + } + if (!*p) { + new->e_argv[cnt] = *argv; + new->e_len[cnt] = 0; + } + } + new->e_argv[cnt] = new->e_orig[cnt] = NULL; + + *argvp = argv + 1; + return (new); +} + +PLAN * +c_exit(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + /* not technically true, but otherwise '-print' is implied */ + isoutput = 1; + + new = palloc(N_EXIT, f_always_true); + + if (arg) { + (*argvp)++; + new->exit_val = find_parsenum(new, opt, arg, NULL); + } else + new->exit_val = 0; + + return (new); +} + + +/* + * -false function + */ +int +f_false(PLAN *plan, FTSENT *entry) +{ + + return (0); +} + +PLAN * +c_false(char ***argvp, int isok, char *opt) +{ + return (palloc(N_FALSE, f_false)); +} + + +/* + * -flags [-]flags functions -- + */ +int +f_flags(PLAN *plan, FTSENT *entry) +{ + u_int32_t flags; + + flags = entry->fts_statp->st_flags; + if (plan->flags == F_ATLEAST) + return ((plan->f_data | flags) == flags); + else + return (flags == plan->f_data); + /* NOTREACHED */ +} + +PLAN * +c_flags(char ***argvp, int isok, char *opt) +{ + char *flags = **argvp; + PLAN *new; + u_long flagset; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_FLAGS, f_flags); + + if (*flags == '-') { + new->flags = F_ATLEAST; + ++flags; + } + + flagset = 0; + if ((strcmp(flags, "none") != 0) && + (string_to_flags(&flags, &flagset, NULL) != 0)) + errx(1, "%s: %s: illegal flags string", opt, flags); + new->f_data = flagset; + return (new); +} + +/* + * -follow functions -- + * + * Always true, causes symbolic links to be followed on a global + * basis. + */ +PLAN * +c_follow(char ***argvp, int isok, char *opt) +{ + ftsoptions &= ~FTS_PHYSICAL; + ftsoptions |= FTS_LOGICAL; + + return (palloc(N_FOLLOW, f_always_true)); +} + +/* -fprint functions -- + * + * Causes the current pathame to be written to the defined output file. + */ +int +f_fprint(PLAN *plan, FTSENT *entry) +{ + + if (-1 == fprintf(plan->fprint_file, "%s\n", entry->fts_path)) + warn("fprintf"); + + return(1); + + /* no descriptors are closed; they will be closed by + operating system when this find command exits. */ +} + +PLAN * +c_fprint(char ***argvp, int isok, char *opt) +{ + PLAN *new; + + isoutput = 1; /* do not assume -print */ + + new = palloc(N_FPRINT, f_fprint); + + if (NULL == (new->fprint_file = fopen(**argvp, "w"))) + err(1, "%s: %s: cannot create file", opt, **argvp); + + (*argvp)++; + return (new); +} + +/* + * -fstype functions -- + * + * True if the file is of a certain type. + */ +int +f_fstype(PLAN *plan, FTSENT *entry) +{ + static dev_t curdev; /* need a guaranteed illegal dev value */ + static int first = 1; + struct statvfs sb; + static short val; + static char fstype[sizeof(sb.f_fstypename)]; + char *p, save[2]; + + memset(&save, 0, sizeof save); /* XXX gcc */ + + /* Only check when we cross mount point. */ + if (first || curdev != entry->fts_statp->st_dev) { + curdev = entry->fts_statp->st_dev; + + /* + * Statfs follows symlinks; find wants the link's file system, + * not where it points. + */ + if (entry->fts_info == FTS_SL || + entry->fts_info == FTS_SLNONE) { + if ((p = strrchr(entry->fts_accpath, '/')) != NULL) + ++p; + else + p = entry->fts_accpath; + save[0] = p[0]; + p[0] = '.'; + save[1] = p[1]; + p[1] = '\0'; + + } else + p = NULL; + + if (statvfs(entry->fts_accpath, &sb)) + err(1, "%s", entry->fts_accpath); + + if (p) { + p[0] = save[0]; + p[1] = save[1]; + } + + first = 0; + + /* + * Further tests may need both of these values, so + * always copy both of them. + */ + val = sb.f_flag; + strlcpy(fstype, sb.f_fstypename, sizeof(fstype)); + } + switch (plan->flags) { + case F_MTFLAG: + return (val & plan->mt_data); + case F_MTTYPE: + return (strncmp(fstype, plan->c_data, sizeof(fstype)) == 0); + default: + abort(); + } +} + +PLAN * +c_fstype(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_FSTYPE, f_fstype); + + switch (*arg) { + case 'l': + if (!strcmp(arg, "local")) { + new->flags = F_MTFLAG; + new->mt_data = MNT_LOCAL; + return (new); + } + break; + case 'r': + if (!strcmp(arg, "rdonly")) { + new->flags = F_MTFLAG; + new->mt_data = MNT_RDONLY; + return (new); + } + break; + } + + new->flags = F_MTTYPE; + new->c_data = arg; + return (new); +} + +/* + * -group gname functions -- + * + * True if the file belongs to the group gname. If gname is numeric and + * an equivalent of the getgrnam() function does not return a valid group + * name, gname is taken as a group ID. + */ +int +f_group(PLAN *plan, FTSENT *entry) +{ + + COMPARE(entry->fts_statp->st_gid, plan->g_data); +} + +PLAN * +c_group(char ***argvp, int isok, char *opt) +{ + char *gname = **argvp; + PLAN *new; + struct group *g; + gid_t gid; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_GROUP, f_group); + g = getgrnam(gname); + if (g == NULL) { + if (atoi(gname) == 0 && gname[0] != '0' && + strcmp(gname, "+0") && strcmp(gname, "-0")) + errx(1, "%s: %s: no such group", opt, gname); + gid = find_parsenum(new, "-group", gname, NULL); + + } else { + new->flags = F_EQUAL; + gid = g->gr_gid; + } + + new->g_data = gid; + return (new); +} + +/* + * -inum n functions -- + * + * True if the file has inode # n. + */ +int +f_inum(PLAN *plan, FTSENT *entry) +{ + + COMPARE(entry->fts_statp->st_ino, plan->i_data); +} + +PLAN * +c_inum(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_INUM, f_inum); + new->i_data = find_parsenum(new, opt, arg, NULL); + return (new); +} + +/* + * -links n functions -- + * + * True if the file has n links. + */ +int +f_links(PLAN *plan, FTSENT *entry) +{ + + COMPARE(entry->fts_statp->st_nlink, plan->l_data); +} + +PLAN * +c_links(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_LINKS, f_links); + new->l_data = (nlink_t)find_parsenum(new, opt, arg, NULL); + return (new); +} + +/* + * -ls functions -- + * + * Always true - prints the current entry to stdout in "ls" format. + */ +int +f_ls(PLAN *plan, FTSENT *entry) +{ + + printlong(entry->fts_path, entry->fts_accpath, entry->fts_statp); + return (1); +} + +PLAN * +c_ls(char ***argvp, int isok, char *opt) +{ + + ftsoptions &= ~FTS_NOSTAT; + isoutput = 1; + + return (palloc(N_LS, f_ls)); +} + +/* + * - maxdepth n functions -- + * + * True if the current search depth is less than or equal to the + * maximum depth specified + */ +int +f_maxdepth(PLAN *plan, FTSENT *entry) +{ + extern FTS *tree; + + if (entry->fts_level >= plan->max_data) + fts_set(tree, entry, FTS_SKIP); + return (entry->fts_level <= plan->max_data); +} + +PLAN * +c_maxdepth(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + new = palloc(N_MAXDEPTH, f_maxdepth); + new->max_data = atoi(arg); + return (new); +} + +/* + * - mindepth n functions -- + * + * True if the current search depth is greater than or equal to the + * minimum depth specified + */ +int +f_mindepth(PLAN *plan, FTSENT *entry) +{ + return (entry->fts_level >= plan->min_data); +} + +PLAN * +c_mindepth(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + new = palloc(N_MINDEPTH, f_mindepth); + new->min_data = atoi(arg); + return (new); +} + +/* + * -mmin n functions -- + * + * True if the difference between the file modification time and the + * current time is n 24 hour periods. + */ +int +f_mmin(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_mtime + SECSPERMIN - 1) / + SECSPERMIN, plan->t_data); +} + +PLAN * +c_mmin(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_MMIN, f_mmin); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_MMIN); + return (new); +} + +/* + * -mtime n functions -- + * + * True if the difference between the file modification time and the + * current time is n 24 hour periods. + */ +int +f_mtime(PLAN *plan, FTSENT *entry) +{ + COMPARE((now - entry->fts_statp->st_mtime + SECSPERDAY - 1) / + SECSPERDAY, plan->t_data); +} + +PLAN * +c_mtime(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_MTIME, f_mtime); + new->t_data = find_parsenum(new, opt, arg, NULL); + TIME_CORRECT(new, N_MTIME); + return (new); +} + +/* + * -name functions -- + * + * True if the basename of the filename being examined + * matches pattern using Pattern Matching Notation S3.14 + */ +int +f_name(PLAN *plan, FTSENT *entry) +{ + + return (!fnmatch(plan->c_data, entry->fts_name, 0)); +} + +PLAN * +c_name(char ***argvp, int isok, char *opt) +{ + char *pattern = **argvp; + PLAN *new; + + (*argvp)++; + new = palloc(N_NAME, f_name); + new->c_data = pattern; + return (new); +} + +/* + * -iname functions -- + * + * Similar to -name, but does case insensitive matching + * + */ +int +f_iname(PLAN *plan, FTSENT *entry) +{ + return (!fnmatch(plan->c_data, entry->fts_name, FNM_CASEFOLD)); +} + +PLAN * +c_iname(char ***argvp, int isok, char *opt) +{ + char *pattern = **argvp; + PLAN *new; + + (*argvp)++; + new = palloc(N_INAME, f_iname); + new->c_data = pattern; + return (new); +} + +/* + * -newer file functions -- + * + * True if the current file has been modified more recently + * than the modification time of the file named by the pathname + * file. + */ +int +f_newer(PLAN *plan, FTSENT *entry) +{ + + return timespeccmp(&entry->fts_statp->st_mtim, &plan->ts_data, >); +} + +PLAN * +c_newer(char ***argvp, int isok, char *opt) +{ + char *filename = **argvp; + PLAN *new; + struct stat sb; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + if (stat(filename, &sb)) + err(1, "%s: %s", opt, filename); + new = palloc(N_NEWER, f_newer); + new->ts_data = sb.st_mtim; + return (new); +} + +/* + * -nogroup functions -- + * + * True if file belongs to a user ID for which the equivalent + * of the getgrnam() 9.2.1 [POSIX.1] function returns NULL. + */ +int +f_nogroup(PLAN *plan, FTSENT *entry) +{ + + return (group_from_gid(entry->fts_statp->st_gid, 1) ? 0 : 1); +} + +PLAN * +c_nogroup(char ***argvp, int isok, char *opt) +{ + ftsoptions &= ~FTS_NOSTAT; + + return (palloc(N_NOGROUP, f_nogroup)); +} + +/* + * -nouser functions -- + * + * True if file belongs to a user ID for which the equivalent + * of the getpwuid() 9.2.2 [POSIX.1] function returns NULL. + */ +int +f_nouser(PLAN *plan, FTSENT *entry) +{ + + return (user_from_uid(entry->fts_statp->st_uid, 1) ? 0 : 1); +} + +PLAN * +c_nouser(char ***argvp, int isok, char *opt) +{ + ftsoptions &= ~FTS_NOSTAT; + + return (palloc(N_NOUSER, f_nouser)); +} + +/* + * -path functions -- + * + * True if the path of the filename being examined + * matches pattern using Pattern Matching Notation S3.14 + */ +int +f_path(PLAN *plan, FTSENT *entry) +{ + + return (!fnmatch(plan->c_data, entry->fts_path, 0)); +} + +PLAN * +c_path(char ***argvp, int isok, char *opt) +{ + char *pattern = **argvp; + PLAN *new; + + (*argvp)++; + new = palloc(N_NAME, f_path); + new->c_data = pattern; + return (new); +} + +/* + * -perm functions -- + * + * The mode argument is used to represent file mode bits. If it starts + * with a leading digit, it's treated as an octal mode, otherwise as a + * symbolic mode. + */ +int +f_perm(PLAN *plan, FTSENT *entry) +{ + mode_t mode; + + mode = entry->fts_statp->st_mode & + (S_ISUID|S_ISGID|S_ISTXT|S_IRWXU|S_IRWXG|S_IRWXO); + if (plan->flags == F_ATLEAST) + return ((plan->m_data | mode) == mode); + else + return (mode == plan->m_data); + /* NOTREACHED */ +} + +PLAN * +c_perm(char ***argvp, int isok, char *opt) +{ + char *perm = **argvp; + PLAN *new; + mode_t *set; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_PERM, f_perm); + + if (*perm == '-') { + new->flags = F_ATLEAST; + ++perm; + } + + if ((set = setmode(perm)) == NULL) + err(1, "%s: Cannot set file mode `%s'", opt, perm); + + new->m_data = getmode(set, 0); + free(set); + return (new); +} + +/* + * -print functions -- + * + * Always true, causes the current pathame to be written to + * standard output. + */ +int +f_print(PLAN *plan, FTSENT *entry) +{ + + (void)printf("%s\n", entry->fts_path); + return (1); +} + +int +f_print0(PLAN *plan, FTSENT *entry) +{ + + (void)fputs(entry->fts_path, stdout); + (void)fputc('\0', stdout); + return (1); +} + +int +f_printx(PLAN *plan, FTSENT *entry) +{ + char *cp; + + for (cp = entry->fts_path; *cp; cp++) { + if (*cp == '\'' || *cp == '\"' || *cp == ' ' || + *cp == '$' || *cp == '`' || + *cp == '\t' || *cp == '\n' || *cp == '\\') + fputc('\\', stdout); + + fputc(*cp, stdout); + } + + fputc('\n', stdout); + return (1); +} + +PLAN * +c_print(char ***argvp, int isok, char *opt) +{ + + isoutput = 1; + + return (palloc(N_PRINT, f_print)); +} + +PLAN * +c_print0(char ***argvp, int isok, char *opt) +{ + + isoutput = 1; + + return (palloc(N_PRINT0, f_print0)); +} + +PLAN * +c_printx(char ***argvp, int isok, char *opt) +{ + + isoutput = 1; + + return (palloc(N_PRINTX, f_printx)); +} + +/* + * -prune functions -- + * + * Prune a portion of the hierarchy. + */ +int +f_prune(PLAN *plan, FTSENT *entry) +{ + if (fts_set(tree, entry, FTS_SKIP)) + err(1, "%s", entry->fts_path); + return (1); +} + +PLAN * +c_prune(char ***argvp, int isok, char *opt) +{ + + return (palloc(N_PRUNE, f_prune)); +} + +/* + * -regex regexp (and related) functions -- + * + * True if the complete file path matches the regular expression regexp. + * For -regex, regexp is a case-sensitive (basic) regular expression. + * For -iregex, regexp is a case-insensitive (basic) regular expression. + */ +int +f_regex(PLAN *plan, FTSENT *entry) +{ + + return (regexec(&plan->regexp_data, entry->fts_path, 0, NULL, 0) == 0); +} + +static PLAN * +c_regex_common(char ***argvp, int isok, enum ntype type, bool icase) +{ + char errbuf[LINE_MAX]; + regex_t reg; + char *regexp = **argvp; + char *lineregexp; + PLAN *new; + int rv; + size_t len; + + (*argvp)++; + + len = strlen(regexp) + 1 + 6; + lineregexp = malloc(len); /* max needed */ + if (lineregexp == NULL) + err(1, NULL); + snprintf(lineregexp, len, "^%s(%s%s)$", + (regcomp_flags & REG_EXTENDED) ? "" : "\\", regexp, + (regcomp_flags & REG_EXTENDED) ? "" : "\\"); + rv = regcomp(®, lineregexp, REG_NOSUB|regcomp_flags| + (icase ? REG_ICASE : 0)); + free(lineregexp); + if (rv != 0) { + regerror(rv, ®, errbuf, sizeof errbuf); + errx(1, "regexp %s: %s", regexp, errbuf); + } + + new = palloc(type, f_regex); + new->regexp_data = reg; + return (new); +} + +PLAN * +c_regex(char ***argvp, int isok, char *opt) +{ + + return (c_regex_common(argvp, isok, N_REGEX, false)); +} + +PLAN * +c_iregex(char ***argvp, int isok, char *opt) +{ + + return (c_regex_common(argvp, isok, N_IREGEX, true)); +} + +/* + * -since "timestamp" functions -- + * + * True if the file modification time is greater than the timestamp value + */ +int +f_since(PLAN *plan, FTSENT *entry) +{ + COMPARE(entry->fts_statp->st_mtime, plan->t_data); +} + +PLAN * +c_since(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_SINCE, f_since); + new->t_data = find_parsedate(new, opt, arg); + new->flags = F_GREATER; + return (new); +} + +/* + * -size n[c] functions -- + * + * True if the file size in bytes, divided by an implementation defined + * value and rounded up to the next integer, is n. If n is followed by + * a c, the size is in bytes. + */ +#define FIND_SIZE 512 +static int divsize = 1; + +int +f_size(PLAN *plan, FTSENT *entry) +{ + off_t size; + + size = divsize ? (entry->fts_statp->st_size + FIND_SIZE - 1) / + FIND_SIZE : entry->fts_statp->st_size; + COMPARE(size, plan->o_data); +} + +PLAN * +c_size(char ***argvp, int isok, char *opt) +{ + char *arg = **argvp; + PLAN *new; + char endch; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_SIZE, f_size); + endch = 'c'; + new->o_data = find_parsenum(new, opt, arg, &endch); + if (endch == 'c') + divsize = 0; + return (new); +} + +/* + * -type c functions -- + * + * True if the type of the file is c, where c is b, c, d, p, f or w + * for block special file, character special file, directory, FIFO, + * regular file or whiteout respectively. + */ +int +f_type(PLAN *plan, FTSENT *entry) +{ + + return ((entry->fts_statp->st_mode & S_IFMT) == plan->m_data); +} + +PLAN * +c_type(char ***argvp, int isok, char *opt) +{ + char *typestring = **argvp; + PLAN *new; + mode_t mask = (mode_t)0; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + switch (typestring[0]) { + case 'b': + mask = S_IFBLK; + break; + case 'c': + mask = S_IFCHR; + break; + case 'd': + mask = S_IFDIR; + break; + case 'f': + mask = S_IFREG; + break; + case 'l': + mask = S_IFLNK; + break; + case 'p': + mask = S_IFIFO; + break; + case 's': + mask = S_IFSOCK; + break; +#ifdef S_IFWHT + case 'W': + case 'w': + mask = S_IFWHT; +#ifdef FTS_WHITEOUT + ftsoptions |= FTS_WHITEOUT; +#endif + break; +#endif /* S_IFWHT */ + default: + errx(1, "%s: %s: unknown type", opt, typestring); + } + + new = palloc(N_TYPE, f_type); + new->m_data = mask; + return (new); +} + +/* + * -user uname functions -- + * + * True if the file belongs to the user uname. If uname is numeric and + * an equivalent of the getpwnam() S9.2.2 [POSIX.1] function does not + * return a valid user name, uname is taken as a user ID. + */ +int +f_user(PLAN *plan, FTSENT *entry) +{ + + COMPARE(entry->fts_statp->st_uid, plan->u_data); +} + +PLAN * +c_user(char ***argvp, int isok, char *opt) +{ + char *username = **argvp; + PLAN *new; + struct passwd *p; + uid_t uid; + + (*argvp)++; + ftsoptions &= ~FTS_NOSTAT; + + new = palloc(N_USER, f_user); + p = getpwnam(username); + if (p == NULL) { + if (atoi(username) == 0 && username[0] != '0' && + strcmp(username, "+0") && strcmp(username, "-0")) + errx(1, "%s: %s: no such user", opt, username); + uid = find_parsenum(new, opt, username, NULL); + + } else { + new->flags = F_EQUAL; + uid = p->pw_uid; + } + + new->u_data = uid; + return (new); +} + +/* + * -xdev functions -- + * + * Always true, causes find not to descend past directories that have a + * different device ID (st_dev, see stat() S5.6.2 [POSIX.1]) + */ +PLAN * +c_xdev(char ***argvp, int isok, char *opt) +{ + ftsoptions |= FTS_XDEV; + + return (palloc(N_XDEV, f_always_true)); +} + +/* + * ( expression ) functions -- + * + * True if expression is true. + */ +int +f_expr(PLAN *plan, FTSENT *entry) +{ + PLAN *p; + int state; + + state = 0; + for (p = plan->p_data[0]; + p && (state = (p->eval)(p, entry)); p = p->next); + return (state); +} + +/* + * N_OPENPAREN and N_CLOSEPAREN nodes are temporary place markers. They are + * eliminated during phase 2 of find_formplan() --- the '(' node is converted + * to a N_EXPR node containing the expression and the ')' node is discarded. + */ +PLAN * +c_openparen(char ***argvp, int isok, char *opt) +{ + + return (palloc(N_OPENPAREN, (int (*)(PLAN *, FTSENT *))-1)); +} + +PLAN * +c_closeparen(char ***argvp, int isok, char *opt) +{ + + return (palloc(N_CLOSEPAREN, (int (*)(PLAN *, FTSENT *))-1)); +} + +/* + * ! expression functions -- + * + * Negation of a primary; the unary NOT operator. + */ +int +f_not(PLAN *plan, FTSENT *entry) +{ + PLAN *p; + int state; + + state = 0; + for (p = plan->p_data[0]; + p && (state = (p->eval)(p, entry)); p = p->next); + return (!state); +} + +PLAN * +c_not(char ***argvp, int isok, char *opt) +{ + + return (palloc(N_NOT, f_not)); +} + +/* + * expression -o expression functions -- + * + * Alternation of primaries; the OR operator. The second expression is + * not evaluated if the first expression is true. + */ +int +f_or(PLAN *plan, FTSENT *entry) +{ + PLAN *p; + int state; + + state = 0; + for (p = plan->p_data[0]; + p && (state = (p->eval)(p, entry)); p = p->next); + + if (state) + return (1); + + for (p = plan->p_data[1]; + p && (state = (p->eval)(p, entry)); p = p->next); + return (state); +} + +PLAN * +c_or(char ***argvp, int isok, char *opt) +{ + + return (palloc(N_OR, f_or)); +} + +PLAN * +c_null(char ***argvp, int isok, char *opt) +{ + + return (NULL); +} + + +/* + * plan_cleanup -- + * Check and see if the specified plan has any residual state, + * and if so, clean it up as appropriate. + * + * At the moment, only N_EXEC has state. Two kinds: 1) + * lists of files to feed to subprocesses 2) State on exit + * statusses of past subprocesses. + */ +/* ARGSUSED1 */ +int +plan_cleanup(PLAN *plan, void *arg) +{ + if (plan->type==N_EXEC && plan->ep_narg) + run_f_exec(plan); + + return plan->ep_rval; /* Passed save exit-status up chain */ +} + +static PLAN * +palloc(enum ntype t, int (*f)(PLAN *, FTSENT *)) +{ + PLAN *new; + + if ((new = malloc(sizeof(PLAN))) == NULL) + err(1, NULL); + memset(new, 0, sizeof(PLAN)); + new->type = t; + new->eval = f; + return (new); +} diff --git a/usr.bin/find/ls.c b/usr.bin/find/ls.c new file mode 100644 index 0000000..23b7cfe --- /dev/null +++ b/usr.bin/find/ls.c @@ -0,0 +1,124 @@ +/* $NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)ls.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: ls.c,v 1.21 2011/08/31 16:24:57 plunky Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "find.h" + +/* Derived from the print routines in the ls(1) source code. */ + +static void printlink(char *); +static void printtime(time_t); + +void +printlong(char *name, /* filename to print */ + char *accpath, /* current valid path to filename */ + struct stat *sb) /* stat buffer */ +{ + char modep[15]; + + (void)printf("%7lu %6lld ", (u_long)sb->st_ino, + (long long)sb->st_blocks); + (void)strmode(sb->st_mode, modep); + (void)printf("%s %3lu %-*s %-*s ", modep, (unsigned long)sb->st_nlink, + LOGIN_NAME_MAX, user_from_uid(sb->st_uid, 0), LOGIN_NAME_MAX, + group_from_gid(sb->st_gid, 0)); + + if (S_ISCHR(sb->st_mode) || S_ISBLK(sb->st_mode)) + (void)printf("%3llu,%5llu ", + (unsigned long long)major(sb->st_rdev), + (unsigned long long)minor(sb->st_rdev)); + else + (void)printf("%9lld ", (long long)sb->st_size); + printtime(sb->st_mtime); + (void)printf("%s", name); + if (S_ISLNK(sb->st_mode)) + printlink(accpath); + (void)putchar('\n'); +} + +static void +printtime(time_t ftime) +{ + int i; + char *longstring; + + longstring = ctime(&ftime); + for (i = 4; i < 11; ++i) + (void)putchar(longstring[i]); + +#define SIXMONTHS ((DAYSPERNYEAR / 2) * SECSPERDAY) + if (ftime + SIXMONTHS > time(NULL)) + for (i = 11; i < 16; ++i) + (void)putchar(longstring[i]); + else { + (void)putchar(' '); + for (i = 20; i < 24; ++i) + (void)putchar(longstring[i]); + } + (void)putchar(' '); +} + +static void +printlink(char *name) +{ + int lnklen; + char path[MAXPATHLEN + 1]; + + if ((lnklen = readlink(name, path, sizeof(path) - 1)) == -1) { + warn("%s", name); + return; + } + path[lnklen] = '\0'; + (void)printf(" -> %s", path); +} diff --git a/usr.bin/find/main.c b/usr.bin/find/main.c new file mode 100644 index 0000000..5aab177 --- /dev/null +++ b/usr.bin/find/main.c @@ -0,0 +1,164 @@ +/* $NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $ */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.4 (Berkeley) 5/4/95"; +#else +__COPYRIGHT("@(#) Copyright (c) 1990, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +__RCSID("$NetBSD: main.c,v 1.31 2013/01/24 17:50:08 christos Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "find.h" + +time_t now; /* time find was run */ +int dotfd; /* starting directory */ +int ftsoptions; /* options for the ftsopen(3) call */ +int isdeprecated; /* using deprecated syntax */ +int isdepth; /* do directories on post-order visit */ +int isoutput; /* user specified output operator */ +int issort; /* sort directory entries */ +int isxargs; /* don't permit xargs delimiting chars */ +int regcomp_flags = REG_BASIC; /* regex compilation flags */ + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + char **p, **start; + int ch; + + (void)time(&now); /* initialize the time-of-day */ + (void)setlocale(LC_ALL, ""); + + /* array to hold dir list. at most (argc - 1) elements. */ + p = start = malloc(argc * sizeof (char *)); + if (p == NULL) + err(1, NULL); + + ftsoptions = FTS_NOSTAT | FTS_PHYSICAL; + while ((ch = getopt(argc, argv, "HLPdEf:hsXx")) != -1) + switch (ch) { + case 'H': + ftsoptions &= ~FTS_LOGICAL; + ftsoptions |= FTS_PHYSICAL|FTS_COMFOLLOW; + break; + case 'L': + ftsoptions &= ~(FTS_COMFOLLOW|FTS_PHYSICAL); + ftsoptions |= FTS_LOGICAL; + break; + case 'P': + ftsoptions &= ~(FTS_COMFOLLOW|FTS_LOGICAL); + ftsoptions |= FTS_PHYSICAL; + break; + case 'd': + isdepth = 1; + break; + case 'E': + regcomp_flags = REG_EXTENDED; + break; + case 'f': + *p++ = optarg; + break; + case 'h': + ftsoptions &= ~FTS_PHYSICAL; + ftsoptions |= FTS_LOGICAL; + break; + case 's': + issort = 1; + break; + case 'X': + isxargs = 1; + break; + case 'x': + ftsoptions |= FTS_XDEV; + break; + case '?': + default: + break; + } + + argc -= optind; + argv += optind; + + /* + * Find first option to delimit the file list. The first argument + * that starts with a -, or is a ! or a ( must be interpreted as a + * part of the find expression, according to POSIX .2. + */ + for (; *argv != NULL; *p++ = *argv++) { + if (argv[0][0] == '-') + break; + if ((argv[0][0] == '!' || argv[0][0] == '(') && + argv[0][1] == '\0') + break; + } + + if (p == start) + usage(); + + *p = NULL; + + if ((dotfd = open(".", O_RDONLY | O_CLOEXEC, 0)) == -1) + err(1, "."); + + exit(find_execute(find_formplan(argv), start)); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "Usage: %s [-H | -L | -P] [-dEhsXx] [-f file] " + "file [file ...] [expression]\n", getprogname()); + exit(1); +} diff --git a/usr.bin/find/misc.c b/usr.bin/find/misc.c new file mode 100644 index 0000000..8ba84c0 --- /dev/null +++ b/usr.bin/find/misc.c @@ -0,0 +1,151 @@ +/* $NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $ */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)misc.c 8.2 (Berkeley) 4/1/94"; +#else +__RCSID("$NetBSD: misc.c,v 1.14 2006/10/11 19:51:10 apb Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "find.h" + +/* + * brace_subst -- + * Replace occurrences of {} in orig with path, and place it in a malloced + * area of memory set in store. + */ +void +brace_subst(char *orig, char **store, char *path, int *len) +{ + int nlen, plen, rest; + char ch, *p, *ostore; + + plen = strlen(path); + for (p = *store; (ch = *orig) != '\0'; ++orig) + if (ch == '{' && orig[1] == '}') { + /* Length of string after the {}. */ + rest = strlen(&orig[2]); + + nlen = *len; + while ((p - *store) + plen + rest + 1 > nlen) + nlen *= 2; + + if (nlen > *len) { + ostore = *store; + if ((*store = realloc(ostore, nlen)) == NULL) + err(1, "realloc"); + *len = nlen; + p += *store - ostore; /* Relocate. */ + } + memmove(p, path, plen); + p += plen; + ++orig; + } else + *p++ = ch; + *p = '\0'; +} + +/* + * queryuser -- + * print a message to standard error and then read input from standard + * input. If the input is 'y' then 1 is returned. + */ +int +queryuser(char **argv) +{ + int ch, first, nl; + + (void)fprintf(stderr, "\"%s", *argv); + while (*++argv) + (void)fprintf(stderr, " %s", *argv); + (void)fprintf(stderr, "\"? "); + (void)fflush(stderr); + + first = ch = getchar(); + for (nl = 0;;) { + if (ch == '\n') { + nl = 1; + break; + } + if (ch == EOF) + break; + ch = getchar(); + } + + if (!nl) { + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + } + return (first == 'y'); +} + +/* + * show_path -- + * called on SIGINFO + */ +/* ARGSUSED */ +void +show_path(int sig) +{ + extern FTSENT *g_entry; + int errno_bak; + + if (g_entry == NULL) { + /* + * not initialized yet. + * assumption: pointer assignment is atomic. + */ + return; + } + + errno_bak = errno; + write(STDERR_FILENO, "find path: ", 11); + write(STDERR_FILENO, g_entry->fts_path, g_entry->fts_pathlen); + write(STDERR_FILENO, "\n", 1); + errno = errno_bak; +} diff --git a/usr.bin/find/operator.c b/usr.bin/find/operator.c new file mode 100644 index 0000000..63a6aa4 --- /dev/null +++ b/usr.bin/find/operator.c @@ -0,0 +1,272 @@ +/* $NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)operator.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: operator.c,v 1.10 2014/10/18 08:33:30 snj Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include + +#include "find.h" + +static PLAN *yanknode(PLAN **); +static PLAN *yankexpr(PLAN **); + +/* + * yanknode -- + * destructively removes the top from the plan + */ +static PLAN * +yanknode(PLAN **planp) /* pointer to top of plan (modified) */ +{ + PLAN *node; /* top node removed from the plan */ + + if ((node = (*planp)) == NULL) + return (NULL); + (*planp) = (*planp)->next; + node->next = NULL; + return (node); +} + +/* + * yankexpr -- + * Removes one expression from the plan. This is used mainly by + * paren_squish. In comments below, an expression is either a + * simple node or a N_EXPR node containing a list of simple nodes. + */ +static PLAN * +yankexpr(PLAN **planp) /* pointer to top of plan (modified) */ +{ + PLAN *next; /* temp node holding subexpression results */ + PLAN *node; /* pointer to returned node or expression */ + PLAN *tail; /* pointer to tail of subplan */ + PLAN *subplan; /* pointer to head of ( ) expression */ + + /* first pull the top node from the plan */ + if ((node = yanknode(planp)) == NULL) + return (NULL); + + /* + * If the node is an '(' then we recursively slurp up expressions + * until we find its associated ')'. If it's a closing paren we + * just return it and unwind our recursion; all other nodes are + * complete expressions, so just return them. + */ + if (node->type == N_OPENPAREN) + for (tail = subplan = NULL;;) { + if ((next = yankexpr(planp)) == NULL) + err(1, "(: missing closing ')'"); + /* + * If we find a closing ')' we store the collected + * subplan in our '(' node and convert the node to + * a N_EXPR. The ')' we found is ignored. Otherwise, + * we just continue to add whatever we get to our + * subplan. + */ + if (next->type == N_CLOSEPAREN) { + if (subplan == NULL) + errx(1, "(): empty inner expression"); + node->p_data[0] = subplan; + node->type = N_EXPR; + node->eval = f_expr; + break; + } else { + if (subplan == NULL) + tail = subplan = next; + else { + tail->next = next; + tail = next; + } + tail->next = NULL; + } + } + return (node); +} + +/* + * paren_squish -- + * replaces "parentheisized" plans in our search plan with "expr" nodes. + */ +PLAN * +paren_squish(PLAN *plan) /* plan with ( ) nodes */ +{ + PLAN *expr; /* pointer to next expression */ + PLAN *tail; /* pointer to tail of result plan */ + PLAN *result; /* pointer to head of result plan */ + + result = tail = NULL; + + /* + * the basic idea is to have yankexpr do all our work and just + * collect its results together. + */ + while ((expr = yankexpr(&plan)) != NULL) { + /* + * if we find an unclaimed ')' it means there is a missing + * '(' someplace. + */ + if (expr->type == N_CLOSEPAREN) + errx(1, "): no beginning '('"); + + /* add the expression to our result plan */ + if (result == NULL) + tail = result = expr; + else { + tail->next = expr; + tail = expr; + } + tail->next = NULL; + } + return (result); +} + +/* + * not_squish -- + * compresses "!" expressions in our search plan. + */ +PLAN * +not_squish(PLAN *plan) /* plan to process */ +{ + PLAN *next; /* next node being processed */ + PLAN *node; /* temporary node used in N_NOT processing */ + PLAN *tail; /* pointer to tail of result plan */ + PLAN *result; /* pointer to head of result plan */ + + tail = result = next = NULL; + + while ((next = yanknode(&plan)) != NULL) { + /* + * if we encounter a ( expression ) then look for nots in + * the expr subplan. + */ + if (next->type == N_EXPR) + next->p_data[0] = not_squish(next->p_data[0]); + + /* + * if we encounter a not, then snag the next node and place + * it in the not's subplan. As an optimization we compress + * several not's to zero or one not. + */ + if (next->type == N_NOT) { + int notlevel = 1; + + node = yanknode(&plan); + while (node != NULL && node->type == N_NOT) { + ++notlevel; + node = yanknode(&plan); + } + if (node == NULL) + errx(1, "!: no following expression"); + if (node->type == N_OR) + errx(1, "!: nothing between ! and -o"); + if (node->type == N_EXPR) + node = not_squish(node); + if (notlevel % 2 != 1) + next = node; + else + next->p_data[0] = node; + } + + /* add the node to our result plan */ + if (result == NULL) + tail = result = next; + else { + tail->next = next; + tail = next; + } + tail->next = NULL; + } + return (result); +} + +/* + * or_squish -- + * compresses -o expressions in our search plan. + */ +PLAN * +or_squish(PLAN *plan) /* plan with ors to be squished */ +{ + PLAN *next; /* next node being processed */ + PLAN *tail; /* pointer to tail of result plan */ + PLAN *result; /* pointer to head of result plan */ + + tail = result = next = NULL; + + while ((next = yanknode(&plan)) != NULL) { + /* + * if we encounter a ( expression ) then look for or's in + * the expr subplan. + */ + if (next->type == N_EXPR) + next->p_data[0] = or_squish(next->p_data[0]); + + /* if we encounter a not then look for not's in the subplan */ + if (next->type == N_NOT) + next->p_data[0] = or_squish(next->p_data[0]); + + /* + * if we encounter an or, then place our collected plan in the + * or's first subplan and then recursively collect the + * remaining stuff into the second subplan and return the or. + */ + if (next->type == N_OR) { + if (result == NULL) + errx(1, "-o: no expression before -o"); + next->p_data[0] = result; + next->p_data[1] = or_squish(plan); + if (next->p_data[1] == NULL) + errx(1, "-o: no expression after -o"); + return (next); + } + + /* add the node to our result plan */ + if (result == NULL) + tail = result = next; + else { + tail->next = next; + tail = next; + } + tail->next = NULL; + } + return (result); +} diff --git a/usr.bin/find/option.c b/usr.bin/find/option.c new file mode 100644 index 0000000..0af4447 --- /dev/null +++ b/usr.bin/find/option.c @@ -0,0 +1,194 @@ +/* $NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $ */ + +/*- + * Copyright (c) 1990, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Cimarron D. Taylor of the University of California, Berkeley. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)option.c 8.2 (Berkeley) 4/16/94"; +#else +__RCSID("$NetBSD: option.c,v 1.27 2016/06/13 00:04:40 pgoyette Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include + +#include "find.h" + +int typecompare(const void *, const void *); +static OPTION *option(char *); + +/* NB: the following table must be sorted lexically. */ +static OPTION const options[] = { + { "!", N_NOT, c_not, 0 }, + { "(", N_OPENPAREN, c_openparen, 0 }, + { ")", N_CLOSEPAREN, c_closeparen, 0 }, + { "-a", N_AND, c_null, 0 }, + { "-amin", N_AMIN, c_amin, 1 }, + { "-and", N_AND, c_null, 0 }, + { "-anewer", N_ANEWER, c_anewer, 1 }, + { "-asince", N_ASINCE, c_asince, 1 }, + { "-atime", N_ATIME, c_atime, 1 }, + { "-cmin", N_CMIN, c_cmin, 1 }, + { "-cnewer", N_CNEWER, c_cnewer, 1 }, + { "-csince", N_CSINCE, c_csince, 1 }, + { "-ctime", N_CTIME, c_ctime, 1 }, + { "-delete", N_DELETE, c_delete, 0 }, + { "-depth", N_DEPTH, c_depth, 0 }, + { "-empty", N_EMPTY, c_empty, 0 }, + { "-exec", N_EXEC, c_exec, 1 }, + { "-execdir", N_EXECDIR, c_execdir, 1 }, + { "-exit", N_EXIT, c_exit, 0 }, + { "-false", N_FALSE, c_false, 0 }, + { "-flags", N_FLAGS, c_flags, 1 }, + { "-follow", N_FOLLOW, c_follow, 0 }, + { "-fprint", N_FPRINT, c_fprint, 1 }, + { "-fstype", N_FSTYPE, c_fstype, 1 }, + { "-group", N_GROUP, c_group, 1 }, + { "-iname", N_INAME, c_iname, 1 }, + { "-inum", N_INUM, c_inum, 1 }, + { "-iregex", N_IREGEX, c_iregex, 1 }, + { "-links", N_LINKS, c_links, 1 }, + { "-ls", N_LS, c_ls, 0 }, + { "-maxdepth", N_MAXDEPTH, c_maxdepth, 1 }, + { "-mindepth", N_MINDEPTH, c_mindepth, 1 }, + { "-mmin", N_MMIN, c_mmin, 1 }, + { "-mtime", N_MTIME, c_mtime, 1 }, + { "-name", N_NAME, c_name, 1 }, + { "-newer", N_NEWER, c_newer, 1 }, + +/* Aliases for compatability with Gnu findutils */ + { "-neweraa", N_ANEWER, c_anewer, 1 }, + { "-newerat", N_ASINCE, c_asince, 1 }, + { "-newercc", N_CNEWER, c_cnewer, 1 }, + { "-newerct", N_CSINCE, c_csince, 1 }, + { "-newermm", N_NEWER, c_newer, 1 }, + { "-newermt", N_SINCE, c_since, 1 }, + +/* + * Unimplemented Gnu findutils options + * + * If you implement any of these, be sure to re-sort the table + * in ascii(7) order! + * + { "-newerBB", N_UNIMPL, c_unimpl, 1 }, + { "-newerBa", N_UNIMPL, c_unimpl, 1 }, + { "-newerBc", N_UNIMPL, c_unimpl, 1 }, + { "-newerBm", N_UNIMPL, c_unimpl, 1 }, + { "-newerBt", N_UNIMPL, c_unimpl, 1 }, + { "-neweraB", N_UNIMPL, c_unimpl, 1 }, + { "-newerac", N_UNIMPL, c_unimpl, 1 }, + { "-neweram", N_UNIMPL, c_unimpl, 1 }, + { "-newerca", N_UNIMPL, c_unimpl, 1 }, + { "-newercm", N_UNIMPL, c_unimpl, 1 }, + { "-newercB", N_UNIMPL, c_unimpl, 1 }, + { "-newermB", N_UNIMPL, c_unimpl, 1 }, + { "-newerma", N_UNIMPL, c_unimpl, 1 }, + { "-newermc", N_UNIMPL, c_unimpl, 1 }, + * + */ + + { "-nogroup", N_NOGROUP, c_nogroup, 0 }, + { "-nouser", N_NOUSER, c_nouser, 0 }, + { "-o", N_OR, c_or, 0 }, + { "-ok", N_OK, c_exec, 1 }, + { "-or", N_OR, c_or, 0 }, + { "-path", N_PATH, c_path, 1 }, + { "-perm", N_PERM, c_perm, 1 }, + { "-print", N_PRINT, c_print, 0 }, + { "-print0", N_PRINT0, c_print0, 0 }, + { "-printx", N_PRINTX, c_printx, 0 }, + { "-prune", N_PRUNE, c_prune, 0 }, + { "-regex", N_REGEX, c_regex, 1 }, + { "-rm", N_DELETE, c_delete, 0 }, + { "-since", N_SINCE, c_since, 1 }, + { "-size", N_SIZE, c_size, 1 }, + { "-type", N_TYPE, c_type, 1 }, + { "-user", N_USER, c_user, 1 }, + { "-xdev", N_XDEV, c_xdev, 0 } +}; + +/* + * find_create -- + * create a node corresponding to a command line argument. + * + * TODO: + * add create/process function pointers to node, so we can skip + * this switch stuff. + */ +PLAN * +find_create(char ***argvp) +{ + OPTION *p; + PLAN *new; + char **argv; + char *opt; + + argv = *argvp; + opt = *argv; + + if ((p = option(opt)) == NULL) + errx(1, "%s: unknown option", opt); + ++argv; + if (p->arg && !*argv) + errx(1, "%s: requires additional arguments", opt); + + new = (p->create)(&argv, p->token == N_OK, opt); + + *argvp = argv; + return (new); +} + +static OPTION * +option(char *name) +{ + OPTION tmp; + + tmp.name = name; + return ((OPTION *)bsearch(&tmp, options, + sizeof(options)/sizeof(OPTION), sizeof(OPTION), typecompare)); +} + +int +typecompare(const void *a, const void *b) +{ + + return (strcmp(((const OPTION *)a)->name, ((const OPTION *)b)->name)); +} diff --git a/usr.bin/fold/fold.1 b/usr.bin/fold/fold.1 new file mode 100644 index 0000000..8f3f6a1 --- /dev/null +++ b/usr.bin/fold/fold.1 @@ -0,0 +1,93 @@ +.\" $NetBSD: fold.1,v 1.17 2012/05/12 15:17:15 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)fold.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd May 12, 2012 +.Dt FOLD 1 +.Os +.Sh NAME +.Nm fold +.Nd "fold long lines for finite width output device" +.Sh SYNOPSIS +.Nm +.Op Fl bs +.Op Fl w Ar width +.Op Ar +.Sh DESCRIPTION +.Nm +is a filter which folds the contents of the specified files, +or the standard input if no files are specified, +breaking the lines to have a maximum of 80 characters. +.Pp +The options are as follows: +.Bl -tag -width XwXwidthXX +.It Fl b +Count +.Ar width +in bytes rather than column positions. +.It Fl s +Fold line after the last blank character within the first +.Ar width +column positions (or bytes). +If a blank character does not exist within the width, then +a longer line will still be split at the width. +.It Fl w Ar width +Specifies +.Ar width +to use as a line width, instead of the default 80 characters. +.El +.Sh ENVIRONMENT +.Bl -tag -width indent +.It Ev LC_CTYPE +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr expand 1 , +.Xr fmt 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2008 . +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 1 . +.Sh BUGS +If underlining is present it may be messed up by folding. +.Pp +.Ar Width +should be a multiple of 8 if tabs are present, or the tabs should +be expanded using +.Xr expand 1 +before using +.Nm . diff --git a/usr.bin/fold/fold.c b/usr.bin/fold/fold.c new file mode 100644 index 0000000..52ecc3d --- /dev/null +++ b/usr.bin/fold/fold.c @@ -0,0 +1,248 @@ +/* $NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Kevin Ruddy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1990, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)fold.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: fold.c,v 1.17 2011/09/04 20:24:59 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +#define DEFLINEWIDTH 80 + +static void fold(int); +static int new_column_position(int, wint_t); +__dead static void usage(void); + +static int count_bytes = 0; +static int split_words = 0; + +int +main(int argc, char **argv) +{ + int ch; + int width; + char *p; + + setlocale(LC_CTYPE, ""); + setprogname(argv[0]); + + width = -1; + while ((ch = getopt(argc, argv, "0123456789bsw:")) != -1) + switch (ch) { + case 'b': + count_bytes = 1; + break; + case 's': + split_words = 1; + break; + case 'w': + if ((width = atoi(optarg)) <= 0) + errx(1, "illegal width value"); + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (width == -1) { + p = argv[optind - 1]; + if (p[0] == '-' && p[1] == ch && !p[2]) + width = atoi(++p); + else + width = atoi(argv[optind] + 1); + } + break; + default: + usage(); + } + argv += optind; + argc -= optind; + + if (width == -1) + width = DEFLINEWIDTH; + + if (!*argv) + fold(width); + else for (; *argv; ++argv) + if (!freopen(*argv, "r", stdin)) { + err (1, "%s", *argv); + /* NOTREACHED */ + } else + fold(width); + exit(0); +} + +/* + * Fold the contents of standard input to fit within WIDTH columns + * (or bytes) and write to standard output. + * + * If split_words is set, split the line at the last space character + * on the line. This flag necessitates storing the line in a buffer + * until the current column > width, or a newline or EOF is read. + * + * The buffer can grow larger than WIDTH due to backspaces and carriage + * returns embedded in the input stream. + */ +static void +fold(int width) +{ + static wchar_t *buf = NULL; + wchar_t *nbuf; + static int buf_max = 0; + wint_t ch; + int col, indx, i; + + col = indx = 0; + while ((ch = getwchar()) != WEOF) { + if (ch == L'\n') { + if (indx != 0) { + for (i = 0; i < indx; i++) + putwchar(buf[i]); + } + putwchar(L'\n'); + col = indx = 0; + continue; + } + + col = new_column_position (col, ch); + if (col > width) { + int last_space; + +#ifdef __GNUC__ + last_space = 0; /* XXX gcc */ +#endif + if (split_words) { + for (i = 0, last_space = -1; i < indx; i++) + if (buf[i] == L' ') + last_space = i; + } + + if (split_words && last_space != -1) { + for (i = 0; i < last_space; i++) + putwchar(buf[i]); + + /* increase last_space here, so we skip trailing whitespace */ + last_space++; + wmemmove (buf, buf+last_space, indx-last_space); + + indx -= last_space; + col = 0; + for (i = 0; i < indx; i++) { + col = new_column_position (col, buf[i]); + } + } else { + for (i = 0; i < indx; i++) + putwchar(buf[i]); + col = indx = 0; + } + putwchar('\n'); + + /* calculate the column position for the next line. */ + col = new_column_position (col, ch); + } + + if (indx + 1 > buf_max) { + /* Allocate buffer in LINE_MAX increments */ + if ((nbuf = realloc (buf, buf_max + 2048)) == NULL) { + err (1, "realloc"); + /* NOTREACHED */ + } + buf = nbuf; + buf_max += 2048; + } + buf[indx++] = ch; + } + + if (indx != 0) { + for (i = 0; i < indx; i++) + putwchar(buf[i]); + } +} + +/* + * calculate the column position + */ +static int +new_column_position (int col, wint_t ch) +{ + int w; + + if (!count_bytes) { + switch (ch) { + case L'\b': + if (col > 0) + --col; + break; + case L'\r': + col = 0; + break; + case L'\t': + col = (col + 8) & ~7; + break; + default: + w = wcwidth(ch); + if (w > 0) + col += w; + break; + } + } else { + char dummy[MB_LEN_MAX]; + + /* XXX: we assume stateless encoding */ + col += wcrtomb(dummy, ch, NULL); + } + + return col; +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-bs] [-w width] [file ...]\n", getprogname()); + exit(1); +} + diff --git a/usr.bin/gencat/gencat.1 b/usr.bin/gencat/gencat.1 new file mode 100644 index 0000000..6fcc171 --- /dev/null +++ b/usr.bin/gencat/gencat.1 @@ -0,0 +1,233 @@ +.\" $NetBSD: gencat.1,v 1.14 2018/07/28 08:03:41 wiz Exp $ +.\" +.\" Copyright (c) 2007 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Kee Hinckley and Brian Ginsbach. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.\" Written by Kee Hinckley +.\" +.Dd December 29, 2011 +.Dt GENCAT 1 +.Os +.Sh NAME +.Nm gencat +.Nd generates a Native Language Support (NLS) message catalog file +.Sh SYNOPSIS +.Nm +.Ar catfile +.Op Ar msgfile|- ... +.Sh DESCRIPTION +The +.Nm +utility generates a formatted message catalog +.Ar catfile +from stdin or one or more message source text files +.Ar msgfile . +The file +.Ar catfile +is created if it does not already exist. +If +.Ar catfile +does exist, its messages are included in the new +.Ar catfile . +The new message text defined in +.Ar msgfile +replaces the old message text currently in +.Ar catfile +when the set and message numbers match. +.Pp +The generated message catalog contains message +strings that will be retrieved using the +.Xr catgets 3 +library call. +These messages are dynamically loaded by the +Native Language Support (NLS) library at run time. +Error messages are grouped into sets, and a program can load a +particular set depending on which type, or language, of messages +is desired. +.Ss Message Text Source File Format +The message text source files are text files in the format described below. +Note that the fields of a message text source line are separated by +space or tab characters. +.\" XXX Required by POSIX; the code must be fixed first. Above line should be +.\" a single space or tab character; +.\" any other space or tab characters are considered to be part of the +.\" field contents. +.Bl -tag -width 3n +.It Li $set Ar n comment +Determines the set identifier to be used for all subsequent messages +until the next +.Li $set +or end-of-file. +The +.Ar n +is the set identifier which is defined as a number in the range +.Bo 1 , +.Dv NL_SETMAX Bc . +Set identifiers within a single source file need not be contiguous. +Any string following the set identifier is treated as a comment. +If no +.Li $set +directive is specified in a message text source file, +all messages will be located in the default message set +.Dv NL_SETD . +.It Li $delset Ar n comment +Removes message set +.Ar n +from the catalog. +The +.Ar n +is a set identifier in the range +.Bo 1 , +.Dv NL_SETMAX Bc . +If a message set was created earlier in the +current file, or in a file previously read by the +.Nm +command, this directive will remove it. +Any string following the set identifier is treated as a comment. +.It Li $ Ar comment +A line beginning with +.Li $ +followed by a space or tab character is treated as a comment. +.It Ar m message-text +A message line consists of a message identifier +.Ar m +in the range +.Bo 1 , +.Dv NL_MSGMAX Bc +and the +.Ar message-text . +The +.Ar message-text +is read until the end of the line or a quote character +(if one is specified). +The +.Ar message-text +is stored in the message catalog with +the set identifier specified by the last +.Li $set +directive, and the message identifier +.Ar m . +If the +.Ar message-text +is empty and there is a space or tab character +following the message identifier, +an empty string is stored in the message catalog. +If no +.Ar message-text +is provided, +and if there is no space or tab character following the message +identifier, +the message with the message identifier +.Ar m +in the current set is removed from the catalog. +Message identifiers need not be contiguous within a single set. +The length of +.Ar message-text +must be in the range +.Bo 0 , +.Dv NL_TEXTMAX Bc . +.It Li $quote Ar c +Sets an optional quote character to be used around the +.Ar message-text . +The quote character +.Ar c +may be any character other than white space. +If this is specified, then messages must begin and end with the +quote character. +.\" XXX Remove next sentence when code is fixed for POSIX conformance. +This is useful when messages must contain leading white space. +.\" XXX Replacement when above is removed. +.\" This is useful to make leading and trailing spaces or empty +.\" messages visible. +By default no quote character is used. +If an empty +.Li $quote +directive is specified, then the current quote character is unset. +.El +.Pp +Empty lines +.\" XXX Remove next line when the code is fixed for POSIX conformance. +and leading blanks +in a message text source file are ignored. +Any line beginning with any character other than those +described above is ignored as a syntax error. +.Pp +Text message strings may contain any characters and +the following special characters and escape sequences. +.Bl -column -offset indent ".Sy carriage return" ".Sy Symbol" ".Sy Sequence" +.It Sy Description Ta Sy Symbol Ta Sy Sequence +.It newline Ta NL(LF) Ta Li \en +.It horizontal tab Ta HT Ta Li \et +.It vertical tab Ta VT Ta Li \ev +.It backspace Ta BS Ta Li \eb +.It carriage return Ta CR Ta Li \er +.It form feed Ta FF Ta Li \ef +.It backslash Ta \e Ta Li \e\e +.It bit pattern Ta ddd Ta Li \eddd +.El +.Pp +A bit pattern, +.Li \eddd , +consists of a backslash followed by +one, two, or three octal digits representing the value of the character. +The current quote character, if defined, may be escaped with a backslash +to generate the quote character. +Any character following the backslash ('\e') other than those specified +is ignored. +.Pp +A backslash at the end of the line continues the message onto the next line. +The following two lines are an example of such a message: +.Pp +.Dl 1 This message continues \e +.D1 on the next line +.Pp +Producing the following message: +.Pp +.Dl 1 This message continues on the next line +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr catclose 3 , +.Xr catgets 3 , +.Xr catopen 3 , +.Xr nls 7 +.\" XXX Close but not quite; add when code is fixed. +.\".Sh STANDARDS +.\"The +.\".Nm +.\"utility is compliant with the +.\".St -p1003.1-2004 +.\"standard. +.Sh AUTHORS +.An -nosplit +The Native Language Support (NLS) message catalog facility was +contributed by +.An J.T. Conklin +.Aq Mt jtc@NetBSD.org . +This page was originally written by +.An Kee Hinckley +.Aq Mt nazgul@somewhere.com . diff --git a/usr.bin/gencat/gencat.c b/usr.bin/gencat/gencat.c new file mode 100644 index 0000000..444ce2d --- /dev/null +++ b/usr.bin/gencat/gencat.c @@ -0,0 +1,879 @@ +/* $NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $ */ + +/* + * Copyright (c) 1996 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by J.T. Conklin. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#if defined(__RCSID) && !defined(lint) +__RCSID("$NetBSD: gencat.c,v 1.36 2013/11/27 17:38:11 apb Exp $"); +#endif + +/*********************************************************** +Copyright 1990, by Alfalfa Software Incorporated, Cambridge, Massachusetts. + + All Rights Reserved + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that Alfalfa's name not be used in +advertising or publicity pertaining to distribution of the software +without specific, written prior permission. + +ALPHALPHA DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING +ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL +ALPHALPHA BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, +WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS +SOFTWARE. + +If you make any modifications, bugfixes or other changes to this software +we'd appreciate it if you could send a copy to us so we can keep things +up-to-date. Many thanks. + Kee Hinckley + Alfalfa Software, Inc. + 267 Allston St., #3 + Cambridge, MA 02139 USA + nazgul@alfalfa.com + +******************************************************************/ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#define _NLS_PRIVATE + +#include +#include + +#include /* Needed by arpa/inet.h on NetBSD */ +#include /* Needed for htonl() on POSIX systems */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NL_SETMAX +#define NL_SETMAX 255 +#endif +#ifndef NL_MSGMAX +#define NL_MSGMAX 2048 +#endif + +struct _msgT { + long msgId; + char *str; + LIST_ENTRY(_msgT) entries; +}; + +struct _setT { + long setId; + LIST_HEAD(msghead, _msgT) msghead; + LIST_ENTRY(_setT) entries; +}; + +static LIST_HEAD(sethead, _setT) sethead = LIST_HEAD_INITIALIZER(sethead); +static struct _setT *curSet; + +static const char *curfile; +static char *curline = NULL; +static long lineno = 0; + +static char *cskip(char *); +__dead static void error(const char *); +static char *get_line(int); +static char *getmsg(int, char *, char); +static void warning(const char *, const char *); +static char *wskip(char *); +static char *xstrdup(const char *); +static void *xmalloc(size_t); +static void *xrealloc(void *, size_t); + +static void MCParse(int fd); +static void MCReadCat(int fd); +static void MCWriteCat(int fd); +static void MCDelMsg(int msgId); +static void MCAddMsg(int msgId, const char *msg); +static void MCAddSet(int setId); +static void MCDelSet(int setId); +__dead static void usage(void); + +#define CORRUPT "corrupt message catalog" +#define NOMEMORY "out of memory" + +static void +usage(void) +{ + fprintf(stderr, "usage: %s catfile [msgfile|- ...]\n", getprogname()); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + int ofd, ifd; + char *catfile = NULL; + int c; + int updatecat = 0; + + while ((c = getopt(argc, argv, "")) != -1) { + switch (c) { + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); + /* NOTREACHED */ + } + catfile = *argv++; + + if ((catfile[0] == '-') && (catfile[1] == '\0')) { + ofd = STDOUT_FILENO; + } else { + ofd = open(catfile, O_WRONLY | O_CREAT | O_EXCL, 0666); + if (ofd < 0) { + if (errno == EEXIST) { + if ((ofd = open(catfile, O_RDWR)) < 0) { + err(1, "Unable to open %s", catfile); + /* NOTREACHED */ + } + } else { + err(1, "Unable to create new %s", catfile); + /* NOTREACHED */ + } + curfile = catfile; + updatecat = 1; + MCReadCat(ofd); + if (lseek(ofd, (off_t)0, SEEK_SET) == (off_t)-1) { + err(1, "Unable to seek on %s", catfile); + /* NOTREACHED */ + } + } + } + + if (argc < 2 || (((*argv)[0] == '-') && ((*argv)[1] == '\0'))) { + if (argc > 2) + usage(); + /* NOTREACHED */ + MCParse(STDIN_FILENO); + } else { + for (; *argv; argv++) { + if ((ifd = open(*argv, O_RDONLY)) < 0) + err(1, "Unable to read %s", *argv); + curfile = *argv; + lineno = 0; + MCParse(ifd); + close(ifd); + } + } + + if (updatecat) { + if (ftruncate(ofd, 0) != 0) { + err(1, "Unable to truncate %s", catfile); + /* NOTREACHED */ + } + } + + MCWriteCat(ofd); + exit(0); +} + +static void +warning(const char *cptr, const char *msg) +{ + if (lineno) { + fprintf(stderr, "%s: %s on line %ld, %s\n", + getprogname(), msg, lineno, curfile); + fprintf(stderr, "%s\n", curline); + if (cptr) { + char *tptr; + for (tptr = curline; tptr < cptr; ++tptr) + putc(' ', stderr); + fprintf(stderr, "^\n"); + } + } else { + fprintf(stderr, "%s: %s, %s\n", getprogname(), msg, curfile); + } +} + +static void +error(const char *msg) +{ + warning(NULL, msg); + exit(1); +} + +static void * +xmalloc(size_t len) +{ + void *p; + + if ((p = malloc(len)) == NULL) + errx(1, NOMEMORY); + return (p); +} + +static void * +xrealloc(void *ptr, size_t size) +{ + if ((ptr = realloc(ptr, size)) == NULL) + errx(1, NOMEMORY); + return (ptr); +} + +static char * +xstrdup(const char *str) +{ + char *nstr; + + if ((nstr = strdup(str)) == NULL) + errx(1, NOMEMORY); + return (nstr); +} + +static char * +get_line(int fd) +{ + static long curlen = BUFSIZ; + static char buf[BUFSIZ], *bptr = buf, *bend = buf; + char *cptr, *cend; + long buflen; + + if (!curline) { + curline = xmalloc(curlen); + } + ++lineno; + + cptr = curline; + cend = curline + curlen; + for (;;) { + for (; bptr < bend && cptr < cend; ++cptr, ++bptr) { + if (*bptr == '\n') { + *cptr = '\0'; + ++bptr; + return (curline); + } else + *cptr = *bptr; + } + if (cptr == cend) { + cptr = curline = xrealloc(curline, curlen *= 2); + cend = curline + curlen; + } + if (bptr == bend) { + buflen = read(fd, buf, BUFSIZ); + if (buflen <= 0) { + if (cptr > curline) { + *cptr = '\0'; + return (curline); + } + return (NULL); + } + bend = buf + buflen; + bptr = buf; + } + } +} + +static char * +wskip(char *cptr) +{ + if (!*cptr || !isspace((unsigned char) *cptr)) { + warning(cptr, "expected a space"); + return (cptr); + } + while (*cptr && isspace((unsigned char) *cptr)) + ++cptr; + return (cptr); +} + +static char * +cskip(char *cptr) +{ + if (!*cptr || isspace((unsigned char) *cptr)) { + warning(cptr, "wasn't expecting a space"); + return (cptr); + } + while (*cptr && !isspace((unsigned char) *cptr)) + ++cptr; + return (cptr); +} + +static char * +getmsg(int fd, char *cptr, char quote) +{ + static char *msg = NULL; + static size_t msglen = 0; + size_t clen, i; + int in_quote = 0; + char *tptr; + + if (quote && *cptr == quote) { + ++cptr; + in_quote = 1; + } + + clen = strlen(cptr) + 1; + if (clen > msglen) { + if (msglen) + msg = xrealloc(msg, clen); + else + msg = xmalloc(clen); + msglen = clen; + } + tptr = msg; + + while (*cptr) { + if (quote && *cptr == quote) { + char *tmp; + tmp = cptr + 1; + if (!in_quote) { + /* XXX hard error? */ + warning(cptr, "unexpected quote character, ignoring"); + *tptr++ = *cptr++; + } else { + cptr++; + /* don't use wskip() */ + while (*cptr && isspace((unsigned char) *cptr)) +#ifndef _BACKWARDS_COMPAT + cptr++; +#else + *tptr++ = *cptr++; +#endif + /* XXX hard error? */ + if (*cptr) + warning(tmp, "unexpected extra characters, ignoring"); + in_quote = 0; +#ifndef _BACKWARDS_COMPAT + break; +#endif + } + } else { + if (*cptr == '\\') { + ++cptr; + switch (*cptr) { + case '\0': + cptr = get_line(fd); + if (!cptr) + error("premature end of file"); + msglen += strlen(cptr); + i = tptr - msg; + msg = xrealloc(msg, msglen); + tptr = msg + i; + break; + case 'n': + *tptr++ = '\n'; + ++cptr; + break; + case 't': + *tptr++ = '\t'; + ++cptr; + break; + case 'v': + *tptr++ = '\v'; + ++cptr; + break; + case 'b': + *tptr++ = '\b'; + ++cptr; + break; + case 'r': + *tptr++ = '\r'; + ++cptr; + break; + case 'f': + *tptr++ = '\f'; + ++cptr; + break; + case '\\': + *tptr++ = '\\'; + ++cptr; + break; + default: + if (quote && *cptr == quote) { + *tptr++ = *cptr++; + } else if (isdigit((unsigned char) *cptr)) { + *tptr = 0; + for (i = 0; i < 3; ++i) { + if (!isdigit((unsigned char) *cptr)) + break; + if (*cptr > '7') + warning(cptr, "octal number greater than 7?!"); + *tptr *= 8; + *tptr += (*cptr - '0'); + ++cptr; + } + } else { + warning(cptr, "unrecognized escape sequence"); + } + break; + } + } else { + *tptr++ = *cptr++; + } + } + } + + if (in_quote) + warning(cptr, "unterminated quoted message, ignoring"); + + *tptr = '\0'; + return (msg); +} + +static void +MCParse(int fd) +{ + char *cptr, *str; + int msgid = 0; + int setid = 0; + char quote = 0; + + while ((cptr = get_line(fd))) { + if (*cptr == '$') { + ++cptr; + if (strncmp(cptr, "set", 3) == 0) { + cptr += 3; + cptr = wskip(cptr); + setid = atoi(cptr); + MCAddSet(setid); + msgid = 0; + } else if (strncmp(cptr, "delset", 6) == 0) { + cptr += 6; + cptr = wskip(cptr); + setid = atoi(cptr); + MCDelSet(setid); + } else if (strncmp(cptr, "quote", 5) == 0) { + cptr += 5; + if (!*cptr) + quote = 0; + else { + cptr = wskip(cptr); + if (!*cptr) + quote = 0; + else + quote = *cptr; + } + } else if (isspace((unsigned char) *cptr)) { + ; + } else { + if (*cptr) { + cptr = wskip(cptr); + if (*cptr) + warning(cptr, "unrecognized line"); + } + } + } else { + /* + * First check for (and eat) empty lines.... + */ + if (!*cptr) + continue; + /* + * We have a digit? Start of a message. Else, + * syntax error. + */ + if (isdigit((unsigned char) *cptr)) { + msgid = atoi(cptr); + cptr = cskip(cptr); + if (*cptr) { + cptr = wskip(cptr); + if (!*cptr) { + MCAddMsg(msgid, ""); + continue; + } + } + } else { + warning(cptr, "neither blank line nor start of a message id"); + continue; + } + /* + * If no set directive specified, all messages + * shall be in default message set NL_SETD. + */ + if (setid == 0) { + setid = NL_SETD; + MCAddSet(setid); + } + /* + * If we have a message ID, but no message, + * then this means "delete this message id + * from the catalog". + */ + if (!*cptr) { + MCDelMsg(msgid); + } else { + str = getmsg(fd, cptr, quote); + MCAddMsg(msgid, str); + } + } + } +} + +static void +MCReadCat(int fd) +{ + void *msgcat; /* message catalog data */ + struct _nls_cat_hdr cat_hdr; + struct _nls_set_hdr *set_hdr; + struct _nls_msg_hdr *msg_hdr; + char *strings; + ssize_t n; + int m, s; + int msgno, setno; + + n = read(fd, &cat_hdr, sizeof(cat_hdr)); + if (n < (ssize_t)sizeof(cat_hdr)) { + if (n == 0) + return; /* empty file */ + else if (n == -1) + err(1, "header read"); + else + errx(1, CORRUPT); + } + if (ntohl((uint32_t)cat_hdr.__magic) != _NLS_MAGIC) + errx(1, "%s: bad magic number (%#x)", CORRUPT, cat_hdr.__magic); + + cat_hdr.__mem = ntohl(cat_hdr.__mem); + + cat_hdr.__nsets = ntohl(cat_hdr.__nsets); + cat_hdr.__msg_hdr_offset = ntohl(cat_hdr.__msg_hdr_offset); + cat_hdr.__msg_txt_offset = ntohl(cat_hdr.__msg_txt_offset); + if ((cat_hdr.__mem < 0) || + (cat_hdr.__msg_hdr_offset < 0) || + (cat_hdr.__msg_txt_offset < 0) || + (cat_hdr.__mem < (int32_t)(cat_hdr.__nsets * sizeof(struct _nls_set_hdr))) || + (cat_hdr.__mem < cat_hdr.__msg_hdr_offset) || + (cat_hdr.__mem < cat_hdr.__msg_txt_offset)) + errx(1, "%s: catalog header", CORRUPT); + + msgcat = xmalloc(cat_hdr.__mem); + + n = read(fd, msgcat, cat_hdr.__mem); + if (n < cat_hdr.__mem) { + if (n == -1) + err(1, "data read"); + else + errx(1, CORRUPT); + } + + set_hdr = (struct _nls_set_hdr *)msgcat; + msg_hdr = (struct _nls_msg_hdr *)((char *)msgcat + + cat_hdr.__msg_hdr_offset); + strings = (char *)msgcat + cat_hdr.__msg_txt_offset; + + setno = 0; + for (s = 0; s < cat_hdr.__nsets; s++, set_hdr++) { + set_hdr->__setno = ntohl(set_hdr->__setno); + if (set_hdr->__setno < setno) + errx(1, "%s: bad set number (%d)", + CORRUPT, set_hdr->__setno); + setno = set_hdr->__setno; + + MCAddSet(setno); + + set_hdr->__nmsgs = ntohl(set_hdr->__nmsgs); + set_hdr->__index = ntohl(set_hdr->__index); + if (set_hdr->__nmsgs < 0 || set_hdr->__index < 0) + errx(1, "%s: set header", CORRUPT); + + /* Get the data */ + msgno = 0; + for (m = 0; m < set_hdr->__nmsgs; m++, msg_hdr++) { + msg_hdr->__msgno = ntohl(msg_hdr->__msgno); + msg_hdr->__offset = ntohl(msg_hdr->__offset); + if (msg_hdr->__msgno < msgno) + errx(1, "%s: bad message number (%d)", + CORRUPT, msg_hdr->__msgno); + if ((msg_hdr->__offset < 0) || + ((strings + msg_hdr->__offset) > + ((char *)msgcat + cat_hdr.__mem))) + errx(1, "%s: message header", CORRUPT); + + msgno = msg_hdr->__msgno; + MCAddMsg(msgno, strings + msg_hdr->__offset); + } + } + free(msgcat); +} + +/* + * Write message catalog. + * + * The message catalog is first converted from its internal to its + * external representation in a chunk of memory allocated for this + * purpose. Then the completed catalog is written. This approach + * avoids additional housekeeping variables and/or a lot of seeks + * that would otherwise be required. + */ +static void +MCWriteCat(int fd) +{ + int nsets; /* number of sets */ + int nmsgs; /* number of msgs */ + int string_size; /* total size of string pool */ + int msgcat_size; /* total size of message catalog */ + void *msgcat; /* message catalog data */ + struct _nls_cat_hdr *cat_hdr; + struct _nls_set_hdr *set_hdr; + struct _nls_msg_hdr *msg_hdr; + char *strings; + struct _setT *set; + struct _msgT *msg; + int msg_index; + int msg_offset; + + /* determine number of sets, number of messages, and size of the + * string pool */ + nsets = 0; + nmsgs = 0; + string_size = 0; + + LIST_FOREACH(set, &sethead, entries) { + nsets++; + + LIST_FOREACH(msg, &set->msghead, entries) { + nmsgs++; + string_size += strlen(msg->str) + 1; + } + } + +#ifdef DEBUG + printf("number of sets: %d\n", nsets); + printf("number of msgs: %d\n", nmsgs); + printf("string pool size: %d\n", string_size); +#endif + + /* determine size and then allocate buffer for constructing external + * message catalog representation */ + msgcat_size = sizeof(struct _nls_cat_hdr) + + (nsets * sizeof(struct _nls_set_hdr)) + + (nmsgs * sizeof(struct _nls_msg_hdr)) + + string_size; + + msgcat = xmalloc(msgcat_size); + memset(msgcat, '\0', msgcat_size); + + /* fill in msg catalog header */ + cat_hdr = (struct _nls_cat_hdr *) msgcat; + cat_hdr->__magic = htonl(_NLS_MAGIC); + cat_hdr->__nsets = htonl(nsets); + cat_hdr->__mem = htonl(msgcat_size - sizeof(struct _nls_cat_hdr)); + cat_hdr->__msg_hdr_offset = + htonl(nsets * sizeof(struct _nls_set_hdr)); + cat_hdr->__msg_txt_offset = + htonl(nsets * sizeof(struct _nls_set_hdr) + + nmsgs * sizeof(struct _nls_msg_hdr)); + + /* compute offsets for set & msg header tables and string pool */ + set_hdr = (struct _nls_set_hdr *) ((char *) msgcat + + sizeof(struct _nls_cat_hdr)); + msg_hdr = (struct _nls_msg_hdr *) ((char *) msgcat + + sizeof(struct _nls_cat_hdr) + + nsets * sizeof(struct _nls_set_hdr)); + strings = (char *) msgcat + + sizeof(struct _nls_cat_hdr) + + nsets * sizeof(struct _nls_set_hdr) + + nmsgs * sizeof(struct _nls_msg_hdr); + + msg_index = 0; + msg_offset = 0; + LIST_FOREACH(set, &sethead, entries) { + + nmsgs = 0; + LIST_FOREACH(msg, &set->msghead, entries) { + int32_t msg_len = strlen(msg->str) + 1; + + msg_hdr->__msgno = htonl(msg->msgId); + msg_hdr->__msglen = htonl(msg_len); + msg_hdr->__offset = htonl(msg_offset); + + memcpy(strings, msg->str, msg_len); + strings += msg_len; + msg_offset += msg_len; + + nmsgs++; + msg_hdr++; + } + + set_hdr->__setno = htonl(set->setId); + set_hdr->__nmsgs = htonl(nmsgs); + set_hdr->__index = htonl(msg_index); + msg_index += nmsgs; + set_hdr++; + } + + /* write out catalog. XXX: should this be done in small chunks? */ + write(fd, msgcat, msgcat_size); +} + +static void +MCAddSet(int setId) +{ + struct _setT *p, *q; + + if (setId <= 0) { + error("setId's must be greater than zero"); + /* NOTREACHED */ + } + if (setId > NL_SETMAX) { + error("setId exceeds limit"); + /* NOTREACHED */ + } + + p = LIST_FIRST(&sethead); + q = NULL; + for (; p != NULL && p->setId < setId; q = p, p = LIST_NEXT(p, entries)) + continue; + + if (p && p->setId == setId) { + ; + } else { + p = xmalloc(sizeof(struct _setT)); + memset(p, '\0', sizeof(struct _setT)); + LIST_INIT(&p->msghead); + + p->setId = setId; + + if (q == NULL) { + LIST_INSERT_HEAD(&sethead, p, entries); + } else { + LIST_INSERT_AFTER(q, p, entries); + } + } + + curSet = p; +} + +static void +MCAddMsg(int msgId, const char *str) +{ + struct _msgT *p, *q; + + if (!curSet) + error("can't specify a message when no set exists"); + + if (msgId <= 0) { + error("msgId's must be greater than zero"); + /* NOTREACHED */ + } + if (msgId > NL_MSGMAX) { + error("msgID exceeds limit"); + /* NOTREACHED */ + } + + p = LIST_FIRST(&curSet->msghead); + q = NULL; + for (; p != NULL && p->msgId < msgId; q = p, p = LIST_NEXT(p, entries)) + continue; + + if (p && p->msgId == msgId) { + free(p->str); + } else { + p = xmalloc(sizeof(struct _msgT)); + memset(p, '\0', sizeof(struct _msgT)); + + if (q == NULL) { + LIST_INSERT_HEAD(&curSet->msghead, p, entries); + } else { + LIST_INSERT_AFTER(q, p, entries); + } + } + + p->msgId = msgId; + p->str = xstrdup(str); +} + +static void +MCDelSet(int setId) +{ + struct _setT *set; + struct _msgT *msg; + + if (setId <= 0) { + error("setId's must be greater than zero"); + /* NOTREACHED */ + } + if (setId > NL_SETMAX) { + error("setId exceeds limit"); + /* NOTREACHED */ + } + + set = LIST_FIRST(&sethead); + for (; set != NULL && set->setId < setId; set = LIST_NEXT(set, entries)) + continue; + + if (set && set->setId == setId) { + LIST_REMOVE(set, entries); + while ((msg = LIST_FIRST(&set->msghead)) != NULL) { + LIST_REMOVE(msg, entries); + free(msg->str); + free(msg); + } + free(set); + return; + } + warning(NULL, "specified set doesn't exist"); +} + +static void +MCDelMsg(int msgId) +{ + struct _msgT *msg; + + if (!curSet) + error("you can't delete a message before defining the set"); + + msg = LIST_FIRST(&curSet->msghead); + for (; msg != NULL && msg->msgId < msgId; msg = LIST_NEXT(msg, entries)) + continue; + + if (msg && msg->msgId == msgId) { + LIST_REMOVE(msg, entries); + free(msg->str); + free(msg); + return; + } + warning(NULL, "specified msg doesn't exist"); +} diff --git a/usr.bin/getconf/getconf.1 b/usr.bin/getconf/getconf.1 new file mode 100644 index 0000000..520a688 --- /dev/null +++ b/usr.bin/getconf/getconf.1 @@ -0,0 +1,94 @@ +.\" $NetBSD: getconf.1,v 1.13 2014/04/13 01:45:34 snj Exp $ +.\" +.\" Copyright (c) 1996 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by J.T. Conklin. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd August 9, 2011 +.Dt GETCONF 1 +.Os +.Sh NAME +.Nm getconf +.Nd get configuration values +.Sh SYNOPSIS +.Nm +.Ar system_var +.Nm +.Fl a +.Nm +.Ar path_var +.Ar pathname +.Nm +.Fl a +.Ar pathname +.Sh DESCRIPTION +The +.Nm +utility writes the current value of a configurable system limit or +option variable to the standard output. +.Pp +The +.Ar system_var +argument specifies the system variable to be queried. +The names of the system variables are from +.Xr sysconf 3 +with the leading +.Dq Li _SC_ +removed. +.Pp +The +.Ar path_var +argument specifies the pathname variable to be queried for the specified +.Ar pathname +argument. +The names of the pathname variables are from +.Xr pathconf 2 +with the leading +.Dq Li _PC_ +removed. +.Pp +When invoked with the option +.Fl a , +.Nm +writes a list of all applicable variables and their values to the +standard output, in the format +.Do +.Va name += +.Va value +.Dc . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr pathconf 2 , +.Xr confstr 3 , +.Xr limits 3 , +.Xr sysconf 3 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/getconf/getconf.c b/usr.bin/getconf/getconf.c new file mode 100644 index 0000000..8de472b --- /dev/null +++ b/usr.bin/getconf/getconf.c @@ -0,0 +1,342 @@ +/* $NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $ */ + +/*- + * Copyright (c) 1996, 1998 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by J.T. Conklin. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: getconf.c,v 1.35 2013/12/19 19:11:50 rmind Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct conf_variable +{ + const char *name; + enum { SYSCONF, CONFSTR, PATHCONF, CONSTANT } type; + long value; +}; + +static void print_longvar(const char *, long); +static void print_strvar(const char *, const char *); +static void printvar(const struct conf_variable *, const char *); +static void usage(void) __dead; + +static const struct conf_variable conf_table[] = +{ + { "PATH", CONFSTR, _CS_PATH }, + + /* Utility Limit Minimum Values */ + { "POSIX2_BC_BASE_MAX", CONSTANT, _POSIX2_BC_BASE_MAX }, + { "POSIX2_BC_DIM_MAX", CONSTANT, _POSIX2_BC_DIM_MAX }, + { "POSIX2_BC_SCALE_MAX", CONSTANT, _POSIX2_BC_SCALE_MAX }, + { "POSIX2_BC_STRING_MAX", CONSTANT, _POSIX2_BC_STRING_MAX }, + { "POSIX2_COLL_WEIGHTS_MAX", CONSTANT, _POSIX2_COLL_WEIGHTS_MAX }, + { "POSIX2_EXPR_NEST_MAX", CONSTANT, _POSIX2_EXPR_NEST_MAX }, + { "POSIX2_LINE_MAX", CONSTANT, _POSIX2_LINE_MAX }, + { "POSIX2_RE_DUP_MAX", CONSTANT, _POSIX2_RE_DUP_MAX }, + { "POSIX2_VERSION", CONSTANT, _POSIX2_VERSION }, + + /* POSIX.1 Minimum Values */ + { "_POSIX_AIO_LISTIO_MAX", CONSTANT, _POSIX_AIO_LISTIO_MAX }, + { "_POSIX_AIO_MAX", CONSTANT, _POSIX_AIO_MAX }, + { "_POSIX_ARG_MAX", CONSTANT, _POSIX_ARG_MAX }, + { "_POSIX_CHILD_MAX", CONSTANT, _POSIX_CHILD_MAX }, + { "_POSIX_LINK_MAX", CONSTANT, _POSIX_LINK_MAX }, + { "_POSIX_MAX_CANON", CONSTANT, _POSIX_MAX_CANON }, + { "_POSIX_MAX_INPUT", CONSTANT, _POSIX_MAX_INPUT }, + { "_POSIX_MQ_OPEN_MAX", CONSTANT, _POSIX_MQ_OPEN_MAX }, + { "_POSIX_MQ_PRIO_MAX", CONSTANT, _POSIX_MQ_PRIO_MAX }, + { "_POSIX_NAME_MAX", CONSTANT, _POSIX_NAME_MAX }, + { "_POSIX_NGROUPS_MAX", CONSTANT, _POSIX_NGROUPS_MAX }, + { "_POSIX_OPEN_MAX", CONSTANT, _POSIX_OPEN_MAX }, + { "_POSIX_PATH_MAX", CONSTANT, _POSIX_PATH_MAX }, + { "_POSIX_PIPE_BUF", CONSTANT, _POSIX_PIPE_BUF }, + { "_POSIX_SSIZE_MAX", CONSTANT, _POSIX_SSIZE_MAX }, + { "_POSIX_STREAM_MAX", CONSTANT, _POSIX_STREAM_MAX }, + { "_POSIX_TZNAME_MAX", CONSTANT, _POSIX_TZNAME_MAX }, + + /* Symbolic Utility Limits */ + { "BC_BASE_MAX", SYSCONF, _SC_BC_BASE_MAX }, + { "BC_DIM_MAX", SYSCONF, _SC_BC_DIM_MAX }, + { "BC_SCALE_MAX", SYSCONF, _SC_BC_SCALE_MAX }, + { "BC_STRING_MAX", SYSCONF, _SC_BC_STRING_MAX }, + { "COLL_WEIGHTS_MAX", SYSCONF, _SC_COLL_WEIGHTS_MAX }, + { "EXPR_NEST_MAX", SYSCONF, _SC_EXPR_NEST_MAX }, + { "LINE_MAX", SYSCONF, _SC_LINE_MAX }, + { "RE_DUP_MAX", SYSCONF, _SC_RE_DUP_MAX }, + + /* Optional Facility Configuration Values */ + { "_POSIX2_C_BIND", SYSCONF, _SC_2_C_BIND }, + { "POSIX2_C_DEV", SYSCONF, _SC_2_C_DEV }, + { "POSIX2_CHAR_TERM", SYSCONF, _SC_2_CHAR_TERM }, + { "POSIX2_FORT_DEV", SYSCONF, _SC_2_FORT_DEV }, + { "POSIX2_FORT_RUN", SYSCONF, _SC_2_FORT_RUN }, + { "POSIX2_LOCALEDEF", SYSCONF, _SC_2_LOCALEDEF }, + { "POSIX2_SW_DEV", SYSCONF, _SC_2_SW_DEV }, + { "POSIX2_UPE", SYSCONF, _SC_2_UPE }, + + /* POSIX.1 Configurable System Variables */ + { "AIO_LISTIO_MAX", SYSCONF, _SC_AIO_LISTIO_MAX }, + { "AIO_MAX", SYSCONF, _SC_AIO_MAX }, + { "ARG_MAX", SYSCONF, _SC_ARG_MAX }, + { "CHILD_MAX", SYSCONF, _SC_CHILD_MAX }, + { "CLK_TCK", SYSCONF, _SC_CLK_TCK }, + { "MQ_OPEN_MAX", SYSCONF, _SC_MQ_OPEN_MAX }, + { "MQ_PRIO_MAX", SYSCONF, _SC_MQ_PRIO_MAX }, + { "NGROUPS_MAX", SYSCONF, _SC_NGROUPS_MAX }, + { "OPEN_MAX", SYSCONF, _SC_OPEN_MAX }, + { "STREAM_MAX", SYSCONF, _SC_STREAM_MAX }, + { "TZNAME_MAX", SYSCONF, _SC_TZNAME_MAX }, + { "_POSIX_JOB_CONTROL", SYSCONF, _SC_JOB_CONTROL }, + { "_POSIX_SAVED_IDS", SYSCONF, _SC_SAVED_IDS }, + { "_POSIX_VERSION", SYSCONF, _SC_VERSION }, + + { "LINK_MAX", PATHCONF, _PC_LINK_MAX }, + { "MAX_CANON", PATHCONF, _PC_MAX_CANON }, + { "MAX_INPUT", PATHCONF, _PC_MAX_INPUT }, + { "NAME_MAX", PATHCONF, _PC_NAME_MAX }, + { "PATH_MAX", PATHCONF, _PC_PATH_MAX }, + { "PIPE_BUF", PATHCONF, _PC_PIPE_BUF }, + { "_POSIX_CHOWN_RESTRICTED", PATHCONF, _PC_CHOWN_RESTRICTED }, + { "_POSIX_NO_TRUNC", PATHCONF, _PC_NO_TRUNC }, + { "_POSIX_VDISABLE", PATHCONF, _PC_VDISABLE }, + + /* POSIX.1b Configurable System Variables */ + { "PAGESIZE", SYSCONF, _SC_PAGESIZE }, + { "_POSIX_ASYNCHRONOUS_IO", SYSCONF, _SC_ASYNCHRONOUS_IO }, + { "_POSIX_FSYNC", SYSCONF, _SC_FSYNC }, + { "_POSIX_MAPPED_FILES", SYSCONF, _SC_MAPPED_FILES }, + { "_POSIX_MEMLOCK", SYSCONF, _SC_MEMLOCK }, + { "_POSIX_MEMLOCK_RANGE", SYSCONF, _SC_MEMLOCK_RANGE }, + { "_POSIX_MEMORY_PROTECTION", SYSCONF, _SC_MEMORY_PROTECTION }, + { "_POSIX_MESSAGE_PASSING", SYSCONF, _SC_MESSAGE_PASSING }, + { "_POSIX_MONOTONIC_CLOCK", SYSCONF, _SC_MONOTONIC_CLOCK }, + { "_POSIX_PRIORITY_SCHEDULING", SYSCONF, _SC_PRIORITY_SCHEDULING }, + { "_POSIX_SEMAPHORES", SYSCONF, _SC_SEMAPHORES }, + { "_POSIX_SHARED_MEMORY_OBJECTS", SYSCONF, _SC_SHARED_MEMORY_OBJECTS }, + { "_POSIX_SYNCHRONIZED_IO", SYSCONF, _SC_SYNCHRONIZED_IO }, + { "_POSIX_TIMERS", SYSCONF, _SC_TIMERS }, + + { "_POSIX_SYNC_IO", PATHCONF, _PC_SYNC_IO }, + + /* POSIX.1c Configurable System Variables */ + { "LOGIN_NAME_MAX", SYSCONF, _SC_LOGIN_NAME_MAX }, + { "_POSIX_THREADS", SYSCONF, _SC_THREADS }, + + /* POSIX.1j Configurable System Variables */ + { "_POSIX_BARRIERS", SYSCONF, _SC_BARRIERS }, + { "_POSIX_READER_WRITER_LOCKS", SYSCONF, _SC_READER_WRITER_LOCKS }, + { "_POSIX_SPIN_LOCKS", SYSCONF, _SC_SPIN_LOCKS }, + + /* XPG4.2 Configurable System Variables */ + { "IOV_MAX", SYSCONF, _SC_IOV_MAX }, + { "PAGE_SIZE", SYSCONF, _SC_PAGE_SIZE }, + { "_XOPEN_SHM", SYSCONF, _SC_XOPEN_SHM }, + + /* X/Open CAE Spec. Issue 5 Version 2 Configurable System Variables */ + { "FILESIZEBITS", PATHCONF, _PC_FILESIZEBITS }, + + /* POSIX.1-2001 XSI Option Group Configurable System Variables */ + { "ATEXIT_MAX", SYSCONF, _SC_ATEXIT_MAX }, + + /* POSIX.1-2001 TSF Configurable System Variables */ + { "GETGR_R_SIZE_MAX", SYSCONF, _SC_GETGR_R_SIZE_MAX }, + { "GETPW_R_SIZE_MAX", SYSCONF, _SC_GETPW_R_SIZE_MAX }, + +#ifdef _NETBSD_SOURCE + /* Commonly provided extensions */ + { "NPROCESSORS_CONF", SYSCONF, _SC_NPROCESSORS_CONF }, + { "NPROCESSORS_ONLN", SYSCONF, _SC_NPROCESSORS_ONLN }, +#endif /* _NETBSD_SOURCE */ + + { NULL, CONSTANT, 0L } +}; + +static int a_flag = 0; /* list all variables */ + +int +main(int argc, char **argv) +{ + int ch; + const struct conf_variable *cp; + const char *varname, *pathname; + int found; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "a")) != -1) { + switch (ch) { + case 'a': + a_flag = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (!a_flag) { + if (argc == 0) + usage(); + varname = argv[0]; + argc--; + argv++; + } else + varname = NULL; + + if (argc > 1) + usage(); + pathname = argv[0]; /* may be NULL */ + + found = 0; + for (cp = conf_table; cp->name != NULL; cp++) { + if (a_flag || strcmp(varname, cp->name) == 0) { + /*LINTED weird expression*/ + if ((cp->type == PATHCONF) == (pathname != NULL)) { + printvar(cp, pathname); + found = 1; + } else if (!a_flag) + errx(EXIT_FAILURE, + "%s: invalid variable type", cp->name); + } + } + + if (!a_flag && !found) + errx(EXIT_FAILURE, "%s: unknown variable", varname); + + (void)fflush(stdout); + return ferror(stdout) ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static void +print_longvar(const char *name, long value) +{ + if (a_flag) + (void)printf("%s = %ld\n", name, value); + else + (void)printf("%ld\n", value); +} + +static void +print_strvar(const char *name, const char *sval) +{ + if (a_flag) + (void)printf("%s = %s\n", name, sval); + else + (void)printf("%s\n", sval); +} + +static void +printvar(const struct conf_variable *cp, const char *pathname) +{ + size_t slen; + char *sval; + long val; + + switch (cp->type) { + case CONSTANT: + print_longvar(cp->name, cp->value); + break; + + case CONFSTR: + errno = 0; + slen = confstr((int)cp->value, NULL, 0); + if (slen == 0) { + if (errno != 0) + +out: err(EXIT_FAILURE, "confstr(%ld)", cp->value); + else + print_strvar(cp->name, "undefined"); + } + + if ((sval = malloc(slen)) == NULL) + err(EXIT_FAILURE, "Can't allocate %zu bytes", slen); + + errno = 0; + if (confstr((int)cp->value, sval, slen) == 0) { + if (errno != 0) + goto out; + else + print_strvar(cp->name, "undefined"); + } else + print_strvar(cp->name, sval); + + free(sval); + break; + + case SYSCONF: + errno = 0; + if ((val = sysconf((int)cp->value)) == -1) { + if (errno != 0) + err(EXIT_FAILURE, "sysconf(%ld)", cp->value); + print_strvar(cp->name, "undefined"); + } else + print_longvar(cp->name, val); + break; + + case PATHCONF: + errno = 0; + if ((val = pathconf(pathname, (int)cp->value)) == -1) { + if (errno != 0) { + if (a_flag && errno == EINVAL) { + /* Just skip invalid variables */ + return; + } + err(EXIT_FAILURE, "pathconf(%s, %ld)", + pathname, cp->value); + /* NOTREACHED */ + } + + print_strvar(cp->name, "undefined"); + } else + print_longvar(cp->name, val); + break; + } +} + + +static void +usage(void) +{ + const char *p = getprogname(); + (void)fprintf(stderr, "Usage: %s system_var\n\t%s -a\n" + "\t%s path_var pathname\n\t%s -a pathname\n", p, p, p, p); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/grep/fastgrep.c b/usr.bin/grep/fastgrep.c new file mode 100644 index 0000000..2fcd864 --- /dev/null +++ b/usr.bin/grep/fastgrep.c @@ -0,0 +1,336 @@ +/* $OpenBSD: util.c,v 1.36 2007/10/02 17:59:18 otto Exp $ */ +/* $FreeBSD: head/usr.bin/grep/fastgrep.c 211496 2010-08-19 09:28:59Z des $ */ + +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * Copyright (C) 2008 Gabor Kovesdan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * XXX: This file is a speed up for grep to cover the defects of the + * regex library. These optimizations should practically be implemented + * there keeping this code clean. This is a future TODO, but for the + * meantime, we need to use this workaround. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: fastgrep.c,v 1.5 2011/04/18 03:27:40 joerg Exp $"); + +#include +#include +#include +#include +#include +#include + +#include "grep.h" + +static inline int grep_cmp(const unsigned char *, const unsigned char *, size_t); +static inline void grep_revstr(unsigned char *, int); + +void +fgrepcomp(fastgrep_t *fg, const char *pat) +{ + unsigned int i; + + /* Initialize. */ + fg->len = strlen(pat); + fg->bol = false; + fg->eol = false; + fg->reversed = false; + + fg->pattern = (unsigned char *)grep_strdup(pat); + + /* Preprocess pattern. */ + for (i = 0; i <= UCHAR_MAX; i++) + fg->qsBc[i] = fg->len; + for (i = 1; i < fg->len; i++) + fg->qsBc[fg->pattern[i]] = fg->len - i; +} + +/* + * Returns: -1 on failure, 0 on success + */ +int +fastcomp(fastgrep_t *fg, const char *pat) +{ + unsigned int i; + int firstHalfDot = -1; + int firstLastHalfDot = -1; + int hasDot = 0; + int lastHalfDot = 0; + int shiftPatternLen; + + /* Initialize. */ + fg->len = strlen(pat); + fg->bol = false; + fg->eol = false; + fg->reversed = false; + fg->word = wflag; + + /* Remove end-of-line character ('$'). */ + if (fg->len > 0 && pat[fg->len - 1] == '$') { + fg->eol = true; + fg->len--; + } + + /* Remove beginning-of-line character ('^'). */ + if (pat[0] == '^') { + fg->bol = true; + fg->len--; + pat++; + } + + if (fg->len >= 14 && + memcmp(pat, "[[:<:]]", 7) == 0 && + memcmp(pat + fg->len - 7, "[[:>:]]", 7) == 0) { + fg->len -= 14; + pat += 7; + /* Word boundary is handled separately in util.c */ + fg->word = true; + } + + /* + * pat has been adjusted earlier to not include '^', '$' or + * the word match character classes at the beginning and ending + * of the string respectively. + */ + fg->pattern = grep_malloc(fg->len + 1); + memcpy(fg->pattern, pat, fg->len); + fg->pattern[fg->len] = '\0'; + + /* Look for ways to cheat...er...avoid the full regex engine. */ + for (i = 0; i < fg->len; i++) { + /* Can still cheat? */ + if (fg->pattern[i] == '.') { + hasDot = i; + if (i < fg->len / 2) { + if (firstHalfDot < 0) + /* Closest dot to the beginning */ + firstHalfDot = i; + } else { + /* Closest dot to the end of the pattern. */ + lastHalfDot = i; + if (firstLastHalfDot < 0) + firstLastHalfDot = i; + } + } else { + /* Free memory and let others know this is empty. */ + free(fg->pattern); + fg->pattern = NULL; + return (-1); + } + } + + /* + * Determine if a reverse search would be faster based on the placement + * of the dots. + */ + if ((!(lflag || cflag)) && ((!(fg->bol || fg->eol)) && + ((lastHalfDot) && ((firstHalfDot < 0) || + ((fg->len - (lastHalfDot + 1)) < (size_t)firstHalfDot)))) && + !oflag && !color) { + fg->reversed = true; + hasDot = fg->len - (firstHalfDot < 0 ? + firstLastHalfDot : firstHalfDot) - 1; + grep_revstr(fg->pattern, fg->len); + } + + /* + * Normal Quick Search would require a shift based on the position the + * next character after the comparison is within the pattern. With + * wildcards, the position of the last dot effects the maximum shift + * distance. + * The closer to the end the wild card is the slower the search. A + * reverse version of this algorithm would be useful for wildcards near + * the end of the string. + * + * Examples: + * Pattern Max shift + * ------- --------- + * this 5 + * .his 4 + * t.is 3 + * th.s 2 + * thi. 1 + */ + + /* Adjust the shift based on location of the last dot ('.'). */ + shiftPatternLen = fg->len - hasDot; + + /* Preprocess pattern. */ + for (i = 0; i <= (signed)UCHAR_MAX; i++) + fg->qsBc[i] = shiftPatternLen; + for (i = hasDot + 1; i < fg->len; i++) { + fg->qsBc[fg->pattern[i]] = fg->len - i; + } + + /* + * Put pattern back to normal after pre-processing to allow for easy + * comparisons later. + */ + if (fg->reversed) + grep_revstr(fg->pattern, fg->len); + + return (0); +} + +int +grep_search(fastgrep_t *fg, const unsigned char *data, size_t len, regmatch_t *pmatch) +{ + unsigned int j; + int ret = REG_NOMATCH; + + if (pmatch->rm_so == (ssize_t)len) + return (ret); + + if (fg->bol && pmatch->rm_so != 0) { + pmatch->rm_so = len; + pmatch->rm_eo = len; + return (ret); + } + + /* No point in going farther if we do not have enough data. */ + if (len < fg->len) + return (ret); + + /* Only try once at the beginning or ending of the line. */ + if (fg->bol || fg->eol) { + /* Simple text comparison. */ + /* Verify data is >= pattern length before searching on it. */ + if (len >= fg->len) { + /* Determine where in data to start search at. */ + j = fg->eol ? len - fg->len : 0; + if (!((fg->bol && fg->eol) && (len != fg->len))) + if (grep_cmp(fg->pattern, data + j, + fg->len) == -1) { + pmatch->rm_so = j; + pmatch->rm_eo = j + fg->len; + ret = 0; + } + } + } else if (fg->reversed) { + /* Quick Search algorithm. */ + j = len; + do { + if (grep_cmp(fg->pattern, data + j - fg->len, + fg->len) == -1) { + pmatch->rm_so = j - fg->len; + pmatch->rm_eo = j; + ret = 0; + break; + } + /* Shift if within bounds, otherwise, we are done. */ + if (j == fg->len) + break; + j -= fg->qsBc[data[j - fg->len - 1]]; + } while (j >= fg->len); + } else { + /* Quick Search algorithm. */ + j = pmatch->rm_so; + do { + if (grep_cmp(fg->pattern, data + j, fg->len) == -1) { + pmatch->rm_so = j; + pmatch->rm_eo = j + fg->len; + ret = 0; + break; + } + + /* Shift if within bounds, otherwise, we are done. */ + if (j + fg->len == len) + break; + else + j += fg->qsBc[data[j + fg->len]]; + } while (j <= (len - fg->len)); + } + + return (ret); +} + +/* + * Returns: i >= 0 on failure (position that it failed) + * -1 on success + */ +static inline int +grep_cmp(const unsigned char *pat, const unsigned char *data, size_t len) +{ + size_t size; + wchar_t *wdata, *wpat; + unsigned int i; + + if (iflag) { + if ((size = mbstowcs(NULL, (const char *)data, 0)) == + ((size_t) - 1)) + return (-1); + + wdata = grep_malloc(size * sizeof(wint_t)); + + if (mbstowcs(wdata, (const char *)data, size) == + ((size_t) - 1)) + return (-1); + + if ((size = mbstowcs(NULL, (const char *)pat, 0)) == + ((size_t) - 1)) + return (-1); + + wpat = grep_malloc(size * sizeof(wint_t)); + + if (mbstowcs(wpat, (const char *)pat, size) == ((size_t) - 1)) + return (-1); + for (i = 0; i < len; i++) { + if ((towlower(wpat[i]) == towlower(wdata[i])) || + ((grepbehave != GREP_FIXED) && wpat[i] == L'.')) + continue; + free(wpat); + free(wdata); + return (i); + } + } else { + for (i = 0; i < len; i++) { + if ((pat[i] == data[i]) || ((grepbehave != GREP_FIXED) && + pat[i] == '.')) + continue; + return (i); + } + } + return (-1); +} + +static inline void +grep_revstr(unsigned char *str, int len) +{ + int i; + char c; + + for (i = 0; i < len / 2; i++) { + c = str[i]; + str[i] = str[len - i - 1]; + str[len - i - 1] = c; + } +} diff --git a/usr.bin/grep/file.c b/usr.bin/grep/file.c new file mode 100644 index 0000000..ef057ba --- /dev/null +++ b/usr.bin/grep/file.c @@ -0,0 +1,276 @@ +/* $NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $ */ +/* $FreeBSD: head/usr.bin/grep/file.c 211496 2010-08-19 09:28:59Z des $ */ +/* $OpenBSD: file.c,v 1.11 2010/07/02 20:48:48 nicm Exp $ */ + +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * Copyright (C) 2008-2010 Gabor Kovesdan + * Copyright (C) 2010 Dimitry Andric + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: file.c,v 1.10 2018/08/12 09:03:21 christos Exp $"); + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "grep.h" + +#define MAXBUFSIZ (32 * 1024) +#define LNBUFBUMP 80 + +#ifndef WITHOUT_GZIP +static gzFile gzbufdesc; +#endif +#ifndef WITHOUT_BZ2 +static BZFILE* bzbufdesc; +#endif + +static unsigned char buffer[MAXBUFSIZ]; +static unsigned char *bufpos; +static size_t bufrem; + +static unsigned char *lnbuf; +static size_t lnbuflen; + +static inline int +grep_refill(struct file *f) +{ + ssize_t nr = -1; + int bzerr; + + bufpos = buffer; + bufrem = 0; + +#ifndef WITHOUT_GZIP + if (filebehave == FILE_GZIP) { + nr = gzread(gzbufdesc, buffer, MAXBUFSIZ); + if (nr == -1) + return -1; + } +#endif +#ifndef WITHOUT_BZ2 + if (filebehave == FILE_BZIP && bzbufdesc != NULL) { + nr = BZ2_bzRead(&bzerr, bzbufdesc, buffer, MAXBUFSIZ); + switch (bzerr) { + case BZ_OK: + case BZ_STREAM_END: + /* No problem, nr will be okay */ + break; + case BZ_DATA_ERROR_MAGIC: + /* + * As opposed to gzread(), which simply returns the + * plain file data, if it is not in the correct + * compressed format, BZ2_bzRead() instead aborts. + * + * So, just restart at the beginning of the file again, + * and use plain reads from now on. + */ + BZ2_bzReadClose(&bzerr, bzbufdesc); + bzbufdesc = NULL; + if (lseek(f->fd, 0, SEEK_SET) == -1) + return (-1); + nr = read(f->fd, buffer, MAXBUFSIZ); + break; + default: + /* Make sure we exit with an error */ + nr = -1; + } + if (nr == -1) + return -1; + } +#endif + if (nr == -1) { + nr = read(f->fd, buffer, MAXBUFSIZ); + } + + if (nr < 0) + return (-1); + + bufrem = nr; + return (0); +} + +static inline int +grep_lnbufgrow(size_t newlen) +{ + + if (lnbuflen < newlen) { + lnbuf = grep_realloc(lnbuf, newlen); + lnbuflen = newlen; + } + + return (0); +} + +char * +grep_fgetln(struct file *f, size_t *lenp) +{ + unsigned char *p; + char *ret; + size_t len; + size_t off; + ptrdiff_t diff; + + /* Fill the buffer, if necessary */ + if (bufrem == 0 && grep_refill(f) != 0) + goto error; + + if (bufrem == 0) { + /* Return zero length to indicate EOF */ + *lenp = 0; + return ((char *)bufpos); + } + + /* Look for a newline in the remaining part of the buffer */ + if ((p = memchr(bufpos, line_sep, bufrem)) != NULL) { + ++p; /* advance over newline */ + ret = (char *)bufpos; + len = p - bufpos; + bufrem -= len; + bufpos = p; + *lenp = len; + return (ret); + } + + /* We have to copy the current buffered data to the line buffer */ + for (len = bufrem, off = 0; ; len += bufrem) { + /* Make sure there is room for more data */ + if (grep_lnbufgrow(len + LNBUFBUMP)) + goto error; + memcpy(lnbuf + off, bufpos, len - off); + off = len; + if (grep_refill(f) != 0) + goto error; + if (bufrem == 0) + /* EOF: return partial line */ + break; + if ((p = memchr(bufpos, line_sep, bufrem)) == NULL) + continue; + /* got it: finish up the line (like code above) */ + ++p; + diff = p - bufpos; + len += diff; + if (grep_lnbufgrow(len)) + goto error; + memcpy(lnbuf + off, bufpos, diff); + bufrem -= diff; + bufpos = p; + break; + } + *lenp = len; + return ((char *)lnbuf); + +error: + *lenp = 0; + return (NULL); +} + +static inline struct file * +grep_file_init(struct file *f) +{ + +#ifndef WITHOUT_GZIP + if (filebehave == FILE_GZIP && + (gzbufdesc = gzdopen(f->fd, "r")) == NULL) + goto error; +#endif + +#ifndef WITHOUT_BZ2 + if (filebehave == FILE_BZIP && + (bzbufdesc = BZ2_bzdopen(f->fd, "r")) == NULL) + goto error; +#endif + + /* Fill read buffer, also catches errors early */ + if (grep_refill(f) != 0) + goto error; + + /* Check for binary stuff, if necessary */ + if (!nulldataflag && binbehave != BINFILE_TEXT && + memchr(bufpos, '\0', bufrem) != NULL) + f->binary = true; + + return (f); +error: + close(f->fd); + free(f); + return (NULL); +} + +/* + * Opens a file for processing. + */ +struct file * +grep_open(const char *path) +{ + struct file *f; + + f = grep_malloc(sizeof *f); + memset(f, 0, sizeof *f); + if (path == NULL) { + /* Processing stdin implies --line-buffered. */ + lbflag = true; + f->fd = STDIN_FILENO; + } else if ((f->fd = open(path, O_RDONLY)) == -1) { + free(f); + return (NULL); + } + + return (grep_file_init(f)); +} + +/* + * Closes a file. + */ +void +grep_close(struct file *f) +{ + + close(f->fd); + + /* Reset read buffer and line buffer */ + bufpos = buffer; + bufrem = 0; + + free(lnbuf); + lnbuf = NULL; + lnbuflen = 0; +} diff --git a/usr.bin/grep/grep.1 b/usr.bin/grep/grep.1 new file mode 100644 index 0000000..7446cad --- /dev/null +++ b/usr.bin/grep/grep.1 @@ -0,0 +1,486 @@ +.\" $NetBSD: grep.1,v 1.4 2012/04/08 22:00:38 wiz Exp $ +.\" $FreeBSD: head/usr.bin/grep/grep.1 210652 2010-07-30 14:05:20Z joel $ +.\" $OpenBSD: grep.1,v 1.38 2010/04/05 06:30:59 jmc Exp $ +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)grep.1 8.3 (Berkeley) 4/18/94 +.\" +.Dd April 19, 2011 +.Dt GREP 1 +.Os +.Sh NAME +.Nm grep , egrep , fgrep , +.Nm zgrep , zegrep , zfgrep +.Nd file pattern searcher +.Sh SYNOPSIS +.Nm grep +.Op Fl abcdDEFGHhIiJLlmnOopqRSsUVvwxZz +.Op Fl A Ar num +.Op Fl B Ar num +.Op Fl C Ns Op Ar num +.Op Fl e Ar pattern +.Op Fl f Ar file +.Op Fl Fl binary-files Ns = Ns Ar value +.Op Fl Fl color Ns Op = Ns Ar when +.Op Fl Fl colour Ns Op = Ns Ar when +.Op Fl Fl context Ns Op = Ns Ar num +.Op Fl Fl decompress +.Op Fl Fl label +.Op Fl Fl line-buffered +.Op Ar pattern +.Op Ar +.Sh DESCRIPTION +The +.Nm grep +utility searches any given input files, +selecting lines that match one or more patterns. +By default, a pattern matches an input line if the regular expression +(RE) in the pattern matches the input line +without its trailing newline. +An empty expression matches every line. +Each input line that matches at least one of the patterns is written +to the standard output. +.Pp +.Nm grep +is used for simple patterns and +basic regular expressions +.Pq BREs ; +.Nm egrep +can handle extended regular expressions +.Pq EREs . +See +.Xr re_format 7 +for more information on regular expressions. +.Nm fgrep +is quicker than both +.Nm grep +and +.Nm egrep , +but can only handle fixed patterns +(i.e. it does not interpret regular expressions). +Patterns may consist of one or more lines, +allowing any of the pattern lines to match a portion of the input. +.Pp +.Nm zgrep , +.Nm zegrep , +and +.Nm zfgrep +act like +.Nm grep , +.Nm egrep , +and +.Nm fgrep , +respectively, but accept input files compressed with the +.Xr compress 1 +or +.Xr gzip 1 +compression utilities. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl A Ar num , Fl Fl after-context Ns = Ns Ar num +Print +.Ar num +lines of trailing context after each match. +See also the +.Fl B +and +.Fl C +options. +.It Fl a , Fl Fl text +Treat all files as ASCII text. +Normally +.Nm +will simply print +.Dq Binary file ... matches +if files contain binary characters. +Use of this option forces +.Nm +to output lines matching the specified pattern. +.It Fl B Ar num , Fl Fl before-context Ns = Ns Ar num +Print +.Ar num +lines of leading context before each match. +See also the +.Fl A +and +.Fl C +options. +.It Fl b , Fl Fl byte-offset +The offset in bytes of a matched pattern is +displayed in front of the respective matched line. +.It Fl C Ns Op Ar num , Fl Fl context Ns = Ns Ar num +Print +.Ar num +lines of leading and trailing context surrounding each match. +The default is 2 and is equivalent to +.Fl A +.Ar 2 +.Fl B +.Ar 2 . +Note: +no whitespace may be given between the option and its argument. +.It Fl c , Fl Fl count +Only a count of selected lines is written to standard output. +.It Fl Fl colour Ns = Ns Op Ar when , Fl Fl color Ns = Ns Op Ar when +Mark up the matching text with the expression stored in +.Ev GREP_COLOR +environment variable. +The possible values of when can be `never', `always' or `auto'. +.It Fl D Ar action , Fl Fl devices Ns = Ns Ar action +Specify the demanded action for devices, FIFOs and sockets. +The default action is `read', which means, that they are read +as if they were normal files. +If the action is set to `skip', devices will be silently skipped. +.It Fl d Ar action , Fl Fl directories Ns = Ns Ar action +Specify the demanded action for directories. +It is `read' by default, which means that the directories +are read in the same manner as normal files. +Other possible values are `skip' to silently ignore the +directories, and `recurse' to read them recursively, which +has the same effect as the +.Fl R +and +.Fl r +option. +.It Fl E , Fl Fl extended-regexp +Interpret +.Ar pattern +as an extended regular expression +(i.e. force +.Nm grep +to behave as +.Nm egrep ) . +.It Fl e Ar pattern , Fl Fl regexp Ns = Ns Ar pattern +Specify a pattern used during the search of the input: +an input line is selected if it matches any of the specified patterns. +This option is most useful when multiple +.Fl e +options are used to specify multiple patterns, +or when a pattern begins with a dash +.Pq Sq - . +.It Fl Fl exclude +If specified, it excludes files matching the given +filename pattern from the search. +Note that +.Fl Fl exclude +patterns take priority over +.Fl Fl include +patterns, and if no +.Fl Fl include +pattern is specified, all files are searched that are +not excluded. +Patterns are matched to the full path specified, +not only to the filename component. +.It Fl Fl exclude-dir +If +.Fl R +is specified, it excludes directories matching the +given filename pattern from the search. +Note that +.Fl Fl exclude-dir +patterns take priority over +.Fl Fl include-dir +patterns, and if no +.Fl Fl include-dir +pattern is specified, all directories are searched that are +not excluded. +.It Fl F , Fl Fl fixed-strings +Interpret +.Ar pattern +as a set of fixed strings +(i.e. force +.Nm grep +to behave as +.Nm fgrep ) . +.It Fl f Ar file , Fl Fl file Ns = Ns Ar file +Read one or more newline separated patterns from +.Ar file . +Empty pattern lines match every input line. +Newlines are not considered part of a pattern. +If +.Ar file +is empty, nothing is matched. +.It Fl G , Fl Fl basic-regexp +Interpret +.Ar pattern +as a basic regular expression +(i.e. force +.Nm grep +to behave as traditional +.Nm grep ) . +.It Fl H +Always print filename headers with output lines. +.It Fl h , Fl Fl no-filename +Never print filename headers +.Pq i.e. filenames +with output lines. +.It Fl Fl help +Print a brief help message. +.It Fl I +Ignore binary files. +This option is equivalent to +.Fl Fl binary-file Ns = Ns Ar without-match +option. +.It Fl i , Fl Fl ignore-case +Perform case insensitive matching. +By default, +.Nm grep +is case sensitive. +.It Fl Fl include +If specified, only files matching the +given filename pattern are searched. +Note that +.Fl Fl exclude +patterns take priority over +.Fl Fl include +patterns. +Patterns are matched to the full path specified, +not only to the filename component. +.It Fl Fl include-dir +If +.Fl R +is specified, only directories matching the +given filename pattern are searched. +Note that +.Fl Fl exclude-dir +patterns take priority over +.Fl Fl include-dir +patterns. +.It Fl J, Fl Fl bz2decompress +Decompress the +.Xr bzip2 1 +compressed file before looking for the text. +.It Fl L , Fl Fl files-without-match +Only the names of files not containing selected lines are written to +standard output. +Pathnames are listed once per file searched. +If the standard input is searched, the string +.Dq (standard input) +is written. +.It Fl l , Fl Fl files-with-matches +Only the names of files containing selected lines are written to +standard output. +.Nm grep +will only search a file until a match has been found, +making searches potentially less expensive. +Pathnames are listed once per file searched. +If the standard input is searched, the string +.Dq (standard input) +is written. +.It Fl Fl mmap +Use +.Xr mmap 2 +instead of +.Xr read 2 +to read input, which can result in better performance under some +circumstances but can cause undefined behaviour. +.It Fl m Ar num, Fl Fl max-count Ns = Ns Ar num +Stop reading the file after +.Ar num +matches. +.It Fl n , Fl Fl line-number +Each output line is preceded by its relative line number in the file, +starting at line 1. +The line number counter is reset for each file processed. +This option is ignored if +.Fl c , +.Fl L , +.Fl l , +or +.Fl q +is +specified. +.It Fl O +If +.Fl R +is specified, follow symbolic links only if they were explicitly listed +on the command line. +The default is not to follow symbolic links. +.It Fl o, Fl Fl only-matching +Prints only the matching part of the lines. +.It Fl p +If +.Fl R +is specified, no symbolic links are followed. +This is the default. +.It Fl q , Fl Fl quiet , Fl Fl silent +Quiet mode: +suppress normal output. +.Nm grep +will only search a file until a match has been found, +making searches potentially less expensive. +.It Fl R , Fl r , Fl Fl recursive +Recursively search subdirectories listed. +.It Fl S +If +.Fl R +is specified, all symbolic links are followed. +The default is not to follow symbolic links. +.It Fl s , Fl Fl no-messages +Silent mode. +Nonexistent and unreadable files are ignored +(i.e. their error messages are suppressed). +.It Fl U , Fl Fl binary +Search binary files, but do not attempt to print them. +.It Fl V , Fl Fl version +Display version information and exit. +.It Fl v , Fl Fl invert-match +Selected lines are those +.Em not +matching any of the specified patterns. +.It Fl w , Fl Fl word-regexp +The expression is searched for as a word (as if surrounded by +.Sq [[:<:]] +and +.Sq [[:>:]] ; +see +.Xr re_format 7 ) . +.It Fl x , Fl Fl line-regexp +Only input lines selected against an entire fixed string or regular +expression are considered to be matching lines. +.It Fl y +Equivalent to +.Fl i . +Obsoleted. +.It Fl Z , Fl Fl null +Prints a zero-byte after the file name. +.It Fl z , Fl Fl null-data +Use the zero byte (ASCII NUL) as line separator. +.It Fl Fl binary-files Ns = Ns Ar value +Controls searching and printing of binary files. +Options are +.Ar binary , +the default: search binary files but do not print them; +.Ar without-match : +do not search binary files; +and +.Ar text : +treat all files as text. +.It Fl Fl decompress +Detect input files compressed with +.Xr bzip2 1 +or +.Xr gzip 1 +and decompress them dynamically. +This makes +.Nm grep +behave like +.Nm zgrep . +.It Fl Fl line-buffered +Force output to be line buffered. +By default, output is line buffered when standard output is a terminal +and block buffered otherwise. +.Pp +.El +If no file arguments are specified, the standard input is used. +.Sh EXIT STATUS +The +.Nm grep +utility exits with one of the following values: +.Pp +.Bl -tag -width flag -compact +.It Li 0 +One or more lines were selected. +.It Li 1 +No lines were selected. +.It Li \*(Gt1 +An error occurred. +.El +.Sh EXAMPLES +To find all occurrences of the word +.Sq patricia +in a file: +.Pp +.Dl $ grep 'patricia' myfile +.Pp +To find all occurrences of the pattern +.Ql .Pp +at the beginning of a line: +.Pp +.Dl $ grep '^\e.Pp' myfile +.Pp +The apostrophes ensure the entire expression is evaluated by +.Nm grep +instead of by the user's shell. +The caret +.Ql ^ +matches the null string at the beginning of a line, +and the +.Ql \e +escapes the +.Ql \&. , +which would otherwise match any character. +.Pp +To find all lines in a file which do not contain the words +.Sq foo +or +.Sq bar : +.Pp +.Dl $ grep -v -e 'foo' -e 'bar' myfile +.Pp +A simple example of an extended regular expression: +.Pp +.Dl $ egrep '19|20|25' calendar +.Pp +Peruses the file +.Sq calendar +looking for either 19, 20, or 25. +.Sh SEE ALSO +.Xr ed 1 , +.Xr ex 1 , +.Xr gzip 1 , +.Xr sed 1 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl AaBbCDdGHhIJLmoPRSUVwZ +are extensions to that specification, and the behaviour of the +.Fl f +flag when used with an empty pattern file is left undefined. +.Pp +All long options are provided for compatibility with +GNU versions of this utility. +.Pp +Historic versions of the +.Nm grep +utility also supported the flags +.Op Fl ruy . +This implementation supports those options; +however, their use is strongly discouraged. +.Sh HISTORY +The +.Nm grep +command first appeared in +.At v6 . diff --git a/usr.bin/grep/grep.c b/usr.bin/grep/grep.c new file mode 100644 index 0000000..bad2a73 --- /dev/null +++ b/usr.bin/grep/grep.c @@ -0,0 +1,722 @@ +/* $NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $ */ +/* $FreeBSD: head/usr.bin/grep/grep.c 211519 2010-08-19 22:55:17Z delphij $ */ +/* $OpenBSD: grep.c,v 1.42 2010/07/02 22:18:03 tedu Exp $ */ + +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * Copyright (C) 2008-2009 Gabor Kovesdan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: grep.c,v 1.15 2018/08/12 09:03:21 christos Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "grep.h" + +#ifndef WITHOUT_NLS +#include +nl_catd catalog; +#endif + +/* + * Default messags to use when NLS is disabled or no catalogue + * is found. + */ +const char *errstr[] = { + "", +/* 1*/ "(standard input)", +/* 2*/ "cannot read bzip2 compressed file", +/* 3*/ "unknown %s option", +/* 4*/ "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n", +/* 5*/ "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n", +/* 6*/ "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n", +/* 7*/ "\t[pattern] [file ...]\n", +/* 8*/ "Binary file %s matches\n", +/* 9*/ "%s (BSD grep) %s\n", +}; + +/* Flags passed to regcomp() and regexec() */ +int cflags = 0; +int eflags = REG_STARTEND; + +/* Searching patterns */ +unsigned int patterns, pattern_sz; +char **pattern; +regex_t *r_pattern; +fastgrep_t *fg_pattern; + +/* Filename exclusion/inclusion patterns */ +unsigned int fpatterns, fpattern_sz; +unsigned int dpatterns, dpattern_sz; +struct epat *dpattern, *fpattern; + +/* For regex errors */ +char re_error[RE_ERROR_BUF + 1]; + +/* Command-line flags */ +unsigned long long Aflag; /* -A x: print x lines trailing each match */ +unsigned long long Bflag; /* -B x: print x lines leading each match */ +bool Hflag; /* -H: always print file name */ +bool Lflag; /* -L: only show names of files with no matches */ +bool bflag; /* -b: show block numbers for each match */ +bool cflag; /* -c: only show a count of matching lines */ +bool hflag; /* -h: don't print filename headers */ +bool iflag; /* -i: ignore case */ +bool lflag; /* -l: only show names of files with matches */ +bool mflag; /* -m x: stop reading the files after x matches */ +unsigned long long mcount; /* count for -m */ +bool nflag; /* -n: show line numbers in front of matching lines */ +bool oflag; /* -o: print only matching part */ +bool qflag; /* -q: quiet mode (don't output anything) */ +bool sflag; /* -s: silent mode (ignore errors) */ +bool vflag; /* -v: only show non-matching lines */ +bool wflag; /* -w: pattern must start and end on word boundaries */ +bool xflag; /* -x: pattern must match entire line */ +bool lbflag; /* --line-buffered */ +bool nullflag; /* --null */ +bool nulldataflag; /* --null-data */ +unsigned char line_sep = '\n'; /* 0 for --null-data */ +char *label; /* --label */ +const char *color; /* --color */ +int grepbehave = GREP_BASIC; /* -EFGP: type of the regex */ +int binbehave = BINFILE_BIN; /* -aIU: handling of binary files */ +int filebehave = FILE_STDIO; /* -JZ: normal, gzip or bzip2 file */ +int devbehave = DEV_READ; /* -D: handling of devices */ +int dirbehave = DIR_READ; /* -dRr: handling of directories */ +int linkbehave = LINK_READ; /* -OpS: handling of symlinks */ + +bool dexclude, dinclude; /* --exclude-dir and --include-dir */ +bool fexclude, finclude; /* --exclude and --include */ + +enum { + BIN_OPT = CHAR_MAX + 1, + COLOR_OPT, + DECOMPRESS_OPT, + HELP_OPT, + MMAP_OPT, + LINEBUF_OPT, + LABEL_OPT, + R_EXCLUDE_OPT, + R_INCLUDE_OPT, + R_DEXCLUDE_OPT, + R_DINCLUDE_OPT +}; + +static inline const char *init_color(const char *); + +/* Housekeeping */ +int tail; /* lines left to print */ +bool notfound; /* file not found */ + +extern char *__progname; + +/* + * Prints usage information and returns 2. + */ +__dead static void +usage(void) +{ + fprintf(stderr, getstr(4), __progname); + fprintf(stderr, "%s", getstr(5)); + fprintf(stderr, "%s", getstr(6)); + fprintf(stderr, "%s", getstr(7)); + exit(2); +} + +static const char optstr[] = + "0123456789A:B:C:D:EFGHIJLOPSRUVZabcd:e:f:hilm:nopqrsuvwxyz"; + +struct option long_options[] = +{ + {"binary-files", required_argument, NULL, BIN_OPT}, +#ifndef WITHOUT_GZIP + {"decompress", no_argument, NULL, DECOMPRESS_OPT}, +#endif + {"help", no_argument, NULL, HELP_OPT}, + {"mmap", no_argument, NULL, MMAP_OPT}, + {"line-buffered", no_argument, NULL, LINEBUF_OPT}, + {"label", required_argument, NULL, LABEL_OPT}, + {"color", optional_argument, NULL, COLOR_OPT}, + {"colour", optional_argument, NULL, COLOR_OPT}, + {"exclude", required_argument, NULL, R_EXCLUDE_OPT}, + {"include", required_argument, NULL, R_INCLUDE_OPT}, + {"exclude-dir", required_argument, NULL, R_DEXCLUDE_OPT}, + {"include-dir", required_argument, NULL, R_DINCLUDE_OPT}, + {"after-context", required_argument, NULL, 'A'}, + {"text", no_argument, NULL, 'a'}, + {"before-context", required_argument, NULL, 'B'}, + {"byte-offset", no_argument, NULL, 'b'}, + {"context", optional_argument, NULL, 'C'}, + {"count", no_argument, NULL, 'c'}, + {"devices", required_argument, NULL, 'D'}, + {"directories", required_argument, NULL, 'd'}, + {"extended-regexp", no_argument, NULL, 'E'}, + {"regexp", required_argument, NULL, 'e'}, + {"fixed-strings", no_argument, NULL, 'F'}, + {"file", required_argument, NULL, 'f'}, + {"basic-regexp", no_argument, NULL, 'G'}, + {"no-filename", no_argument, NULL, 'h'}, + {"with-filename", no_argument, NULL, 'H'}, + {"ignore-case", no_argument, NULL, 'i'}, +#ifndef WITHOUT_BZ2 + {"bz2decompress", no_argument, NULL, 'J'}, +#endif + {"files-with-matches", no_argument, NULL, 'l'}, + {"files-without-match", no_argument, NULL, 'L'}, + {"max-count", required_argument, NULL, 'm'}, + {"line-number", no_argument, NULL, 'n'}, + {"only-matching", no_argument, NULL, 'o'}, + {"quiet", no_argument, NULL, 'q'}, + {"silent", no_argument, NULL, 'q'}, + {"recursive", no_argument, NULL, 'r'}, + {"no-messages", no_argument, NULL, 's'}, + {"binary", no_argument, NULL, 'U'}, + {"unix-byte-offsets", no_argument, NULL, 'u'}, + {"invert-match", no_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"word-regexp", no_argument, NULL, 'w'}, + {"line-regexp", no_argument, NULL, 'x'}, + {"null", no_argument, NULL, 'Z'}, + {"null-data", no_argument, NULL, 'z'}, + {NULL, no_argument, NULL, 0} +}; + +/* + * Adds a searching pattern to the internal array. + */ +static void +add_pattern(char *pat, size_t len) +{ + + /* TODO: Check for empty patterns and shortcut */ + + /* Increase size if necessary */ + if (patterns == pattern_sz) { + pattern_sz *= 2; + pattern = grep_realloc(pattern, ++pattern_sz * + sizeof(*pattern)); + } + if (len > 0 && pat[len - 1] == '\n') + --len; + /* pat may not be NUL-terminated */ + pattern[patterns] = grep_malloc(len + 1); + memcpy(pattern[patterns], pat, len); + pattern[patterns][len] = '\0'; + ++patterns; +} + +/* + * Adds a file include/exclude pattern to the internal array. + */ +static void +add_fpattern(const char *pat, int mode) +{ + + /* Increase size if necessary */ + if (fpatterns == fpattern_sz) { + fpattern_sz *= 2; + fpattern = grep_realloc(fpattern, ++fpattern_sz * + sizeof(struct epat)); + } + fpattern[fpatterns].pat = grep_strdup(pat); + fpattern[fpatterns].mode = mode; + ++fpatterns; +} + +/* + * Adds a directory include/exclude pattern to the internal array. + */ +static void +add_dpattern(const char *pat, int mode) +{ + + /* Increase size if necessary */ + if (dpatterns == dpattern_sz) { + dpattern_sz *= 2; + dpattern = grep_realloc(dpattern, ++dpattern_sz * + sizeof(struct epat)); + } + dpattern[dpatterns].pat = grep_strdup(pat); + dpattern[dpatterns].mode = mode; + ++dpatterns; +} + +/* + * Reads searching patterns from a file and adds them with add_pattern(). + */ +static void +read_patterns(const char *fn) +{ + FILE *f; + char *line; + size_t len; + ssize_t rlen; + + if ((f = fopen(fn, "r")) == NULL) + err(2, "%s", fn); + line = NULL; + len = 0; + while ((rlen = getline(&line, &len, f)) != -1) + add_pattern(line, *line == '\n' ? 0 : (size_t)rlen); + free(line); + if (ferror(f)) + err(2, "%s", fn); + fclose(f); +} + +static inline const char * +init_color(const char *d) +{ + char *c; + + c = getenv("GREP_COLOR"); + return (c != NULL ? c : d); +} + +int +main(int argc, char *argv[]) +{ + char **aargv, **eargv, *eopts; + char *ep; + unsigned long long l; + unsigned int aargc, eargc, i, j; + int c, lastc, needpattern, newarg, prevoptind; + + setlocale(LC_ALL, ""); + +#ifndef WITHOUT_NLS + catalog = catopen("grep", NL_CAT_LOCALE); +#endif + + /* Check what is the program name of the binary. In this + way we can have all the funcionalities in one binary + without the need of scripting and using ugly hacks. */ + switch (__progname[0]) { + case 'e': + grepbehave = GREP_EXTENDED; + break; + case 'f': + grepbehave = GREP_FIXED; + break; + case 'g': + grepbehave = GREP_BASIC; + break; +#ifndef WITHOUT_GZIP + case 'z': + filebehave = FILE_GZIP; + switch(__progname[1]) { + case 'e': + grepbehave = GREP_EXTENDED; + break; + case 'f': + grepbehave = GREP_FIXED; + break; + case 'g': + grepbehave = GREP_BASIC; + break; + } + break; +#endif + } + + lastc = '\0'; + newarg = 1; + prevoptind = 1; + needpattern = 1; + + eopts = getenv("GREP_OPTIONS"); + + /* support for extra arguments in GREP_OPTIONS */ + eargc = 0; + if (eopts != NULL) { + char *str; + + /* make an estimation of how many extra arguments we have */ + for (j = 0; j < strlen(eopts); j++) + if (eopts[j] == ' ') + eargc++; + + eargv = (char **)grep_malloc(sizeof(char *) * (eargc + 1)); + + eargc = 0; + /* parse extra arguments */ + while ((str = strsep(&eopts, " ")) != NULL) + eargv[eargc++] = grep_strdup(str); + + aargv = (char **)grep_calloc(eargc + argc + 1, + sizeof(char *)); + + aargv[0] = argv[0]; + for (i = 0; i < eargc; i++) + aargv[i + 1] = eargv[i]; + for (j = 1; j < (unsigned int)argc; j++, i++) + aargv[i + 1] = argv[j]; + + aargc = eargc + argc; + } else { + aargv = argv; + aargc = argc; + } + + while (((c = getopt_long(aargc, aargv, optstr, long_options, NULL)) != + -1)) { + switch (c) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (newarg || !isdigit(lastc)) + Aflag = 0; + else if (Aflag > LLONG_MAX / 10) { + errno = ERANGE; + err(2, NULL); + } + Aflag = Bflag = (Aflag * 10) + (c - '0'); + break; + case 'C': + if (optarg == NULL) { + Aflag = Bflag = 2; + break; + } + /* FALLTHROUGH */ + case 'A': + /* FALLTHROUGH */ + case 'B': + errno = 0; + l = strtoull(optarg, &ep, 10); + if (((errno == ERANGE) && (l == ULLONG_MAX)) || + ((errno == EINVAL) && (l == 0))) + err(2, NULL); + else if (ep[0] != '\0') { + errno = EINVAL; + err(2, NULL); + } + if (c == 'A') + Aflag = l; + else if (c == 'B') + Bflag = l; + else + Aflag = Bflag = l; + break; + case 'a': + binbehave = BINFILE_TEXT; + break; + case 'b': + bflag = true; + break; + case 'c': + cflag = true; + break; + case 'D': + if (strcasecmp(optarg, "skip") == 0) + devbehave = DEV_SKIP; + else if (strcasecmp(optarg, "read") == 0) + devbehave = DEV_READ; + else + errx(2, getstr(3), "--devices"); + break; + case 'd': + if (strcasecmp("recurse", optarg) == 0) { + Hflag = true; + dirbehave = DIR_RECURSE; + } else if (strcasecmp("skip", optarg) == 0) + dirbehave = DIR_SKIP; + else if (strcasecmp("read", optarg) == 0) + dirbehave = DIR_READ; + else + errx(2, getstr(3), "--directories"); + break; + case 'E': + grepbehave = GREP_EXTENDED; + break; + case 'e': + add_pattern(optarg, strlen(optarg)); + needpattern = 0; + break; + case 'F': + grepbehave = GREP_FIXED; + break; + case 'f': + read_patterns(optarg); + needpattern = 0; + break; + case 'G': + grepbehave = GREP_BASIC; + break; + case 'H': + Hflag = true; + break; + case 'h': + Hflag = false; + hflag = true; + break; + case 'I': + binbehave = BINFILE_SKIP; + break; + case 'i': + case 'y': + iflag = true; + cflags |= REG_ICASE; + break; +#ifndef WITHOUT_BZ2 + case 'J': + filebehave = FILE_BZIP; + break; +#endif + case 'L': + lflag = false; + Lflag = true; + break; + case 'l': + Lflag = false; + lflag = true; + break; + case 'm': + mflag = true; + errno = 0; + mcount = strtoull(optarg, &ep, 10); + if (((errno == ERANGE) && (mcount == ULLONG_MAX)) || + ((errno == EINVAL) && (mcount == 0))) + err(2, NULL); + else if (ep[0] != '\0') { + errno = EINVAL; + err(2, NULL); + } + break; + case 'n': + nflag = true; + break; + case 'O': + linkbehave = LINK_EXPLICIT; + break; + case 'o': + oflag = true; + break; + case 'p': + linkbehave = LINK_SKIP; + break; + case 'q': + qflag = true; + break; + case 'S': + linkbehave = LINK_READ; + break; + case 'R': + case 'r': + dirbehave = DIR_RECURSE; + Hflag = true; + break; + case 's': + sflag = true; + break; + case 'U': + binbehave = BINFILE_BIN; + break; + case 'u': + case MMAP_OPT: + /* noop, compatibility */ + break; + case 'V': + printf(getstr(9), __progname, VERSION); + exit(0); + case 'v': + vflag = true; + break; + case 'w': + wflag = true; + break; + case 'x': + xflag = true; + break; + case 'Z': + nullflag = true; + break; + case 'z': + nulldataflag = true; + line_sep = '\0'; + break; + case BIN_OPT: + if (strcasecmp("binary", optarg) == 0) + binbehave = BINFILE_BIN; + else if (strcasecmp("without-match", optarg) == 0) + binbehave = BINFILE_SKIP; + else if (strcasecmp("text", optarg) == 0) + binbehave = BINFILE_TEXT; + else + errx(2, getstr(3), "--binary-files"); + break; + case COLOR_OPT: + color = NULL; + if (optarg == NULL || strcasecmp("auto", optarg) == 0 || + strcasecmp("tty", optarg) == 0 || + strcasecmp("if-tty", optarg) == 0) { + char *term; + + term = getenv("TERM"); + if (isatty(STDOUT_FILENO) && term != NULL && + strcasecmp(term, "dumb") != 0) + color = init_color("01;31"); + } else if (strcasecmp("always", optarg) == 0 || + strcasecmp("yes", optarg) == 0 || + strcasecmp("force", optarg) == 0) { + color = init_color("01;31"); + } else if (strcasecmp("never", optarg) != 0 && + strcasecmp("none", optarg) != 0 && + strcasecmp("no", optarg) != 0) + errx(2, getstr(3), "--color"); + break; +#ifndef WITHOUT_GZIP + case DECOMPRESS_OPT: + filebehave = FILE_GZIP; + break; +#endif + case LABEL_OPT: + label = optarg; + break; + case LINEBUF_OPT: + lbflag = true; + break; + case R_INCLUDE_OPT: + finclude = true; + add_fpattern(optarg, INCL_PAT); + break; + case R_EXCLUDE_OPT: + fexclude = true; + add_fpattern(optarg, EXCL_PAT); + break; + case R_DINCLUDE_OPT: + dinclude = true; + add_dpattern(optarg, INCL_PAT); + break; + case R_DEXCLUDE_OPT: + dexclude = true; + add_dpattern(optarg, EXCL_PAT); + break; + case HELP_OPT: + default: + usage(); + } + lastc = c; + newarg = optind != prevoptind; + prevoptind = optind; + } + aargc -= optind; + aargv += optind; + + /* Fail if we don't have any pattern */ + if (aargc == 0 && needpattern) + usage(); + + /* Process patterns from command line */ + if (aargc != 0 && needpattern) { + add_pattern(*aargv, strlen(*aargv)); + --aargc; + ++aargv; + } + + switch (grepbehave) { + case GREP_FIXED: + case GREP_BASIC: + break; + case GREP_EXTENDED: + cflags |= REG_EXTENDED; + break; + default: + /* NOTREACHED */ + usage(); + } + + fg_pattern = grep_calloc(patterns, sizeof(*fg_pattern)); + r_pattern = grep_calloc(patterns, sizeof(*r_pattern)); +/* + * XXX: fgrepcomp() and fastcomp() are workarounds for regexec() performance. + * Optimizations should be done there. + */ + /* Check if cheating is allowed (always is for fgrep). */ + if (grepbehave == GREP_FIXED) { + for (i = 0; i < patterns; ++i) + fgrepcomp(&fg_pattern[i], pattern[i]); + } else { + for (i = 0; i < patterns; ++i) { + if (fastcomp(&fg_pattern[i], pattern[i])) { + /* Fall back to full regex library */ + c = regcomp(&r_pattern[i], pattern[i], cflags); + if (c != 0) { + regerror(c, &r_pattern[i], re_error, + RE_ERROR_BUF); + errx(2, "%s", re_error); + } + } + } + } + + if (lbflag) { +#ifdef _IOLBF + setvbuf(stdout, NULL, _IOLBF, 0); +#else + setlinebuf(stdout); +#endif + } + + if ((aargc == 0 || aargc == 1) && !Hflag) + hflag = true; + + if (aargc == 0) + exit(!procfile("-")); + + if (dirbehave == DIR_RECURSE) + c = grep_tree(aargv); + else + for (c = 0; aargc--; ++aargv) { + if ((finclude || fexclude) && !file_matching(*aargv)) + continue; + c+= procfile(*aargv); + } + +#ifndef WITHOUT_NLS + catclose(catalog); +#endif + + /* Find out the correct return value according to the + results and the command line option. */ + exit(c ? (notfound ? (qflag ? 0 : 2) : 0) : (notfound ? 2 : 1)); +} diff --git a/usr.bin/grep/grep.h b/usr.bin/grep/grep.h new file mode 100644 index 0000000..b7ef7fa --- /dev/null +++ b/usr.bin/grep/grep.h @@ -0,0 +1,162 @@ +/* $NetBSD: grep.h,v 1.10 2018/08/12 09:03:21 christos Exp $ */ +/* $OpenBSD: grep.h,v 1.15 2010/04/05 03:03:55 tedu Exp $ */ +/* $FreeBSD: head/usr.bin/grep/grep.h 211496 2010-08-19 09:28:59Z des $ */ + +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * Copyright (c) 2008-2009 Gabor Kovesdan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef WITHOUT_BZ2 +#include +#endif +#include +#include +#include +#include +#ifndef WITHOUT_GZIP +#include +#endif + +#ifdef WITHOUT_NLS +#define getstr(n) errstr[n] +#else +#include + +extern nl_catd catalog; +#define getstr(n) catgets(catalog, 1, n, errstr[n]) +#endif + +extern const char *errstr[]; + +#define VERSION "2.5.1-FreeBSD" + +#define GREP_FIXED 0 +#define GREP_BASIC 1 +#define GREP_EXTENDED 2 + +#define BINFILE_BIN 0 +#define BINFILE_SKIP 1 +#define BINFILE_TEXT 2 + +#define FILE_STDIO 0 +#define FILE_GZIP 1 +#define FILE_BZIP 2 + +#define DIR_READ 0 +#define DIR_SKIP 1 +#define DIR_RECURSE 2 + +#define DEV_READ 0 +#define DEV_SKIP 1 + +#define LINK_READ 0 +#define LINK_EXPLICIT 1 +#define LINK_SKIP 2 + +#define EXCL_PAT 0 +#define INCL_PAT 1 + +#define MAX_LINE_MATCHES 32 + +struct file { + int fd; + bool binary; +}; + +struct str { + off_t off; + size_t len; + char *dat; + char *file; + int line_no; +}; + +struct epat { + char *pat; + int mode; +}; + +typedef struct { + size_t len; + unsigned char *pattern; + int qsBc[UCHAR_MAX + 1]; + /* flags */ + bool bol; + bool eol; + bool reversed; + bool word; +} fastgrep_t; + +/* Flags passed to regcomp() and regexec() */ +extern int cflags, eflags; + +/* Command line flags */ +extern bool Eflag, Fflag, Gflag, Hflag, Lflag, + bflag, cflag, hflag, iflag, lflag, mflag, nflag, oflag, + qflag, sflag, vflag, wflag, xflag; +extern bool dexclude, dinclude, fexclude, finclude, lbflag, nullflag, nulldataflag; +extern unsigned char line_sep; +extern unsigned long long Aflag, Bflag, mcount; +extern char *label; +extern const char *color; +extern int binbehave, devbehave, dirbehave, filebehave, grepbehave, linkbehave; + +extern bool notfound; +extern int tail; +extern unsigned int dpatterns, fpatterns, patterns; +extern char **pattern; +extern struct epat *dpattern, *fpattern; +extern regex_t *er_pattern, *r_pattern; +extern fastgrep_t *fg_pattern; + +/* For regex errors */ +#define RE_ERROR_BUF 512 +extern char re_error[RE_ERROR_BUF + 1]; /* Seems big enough */ + +/* util.c */ +bool file_matching(const char *fname); +int procfile(const char *fn); +int grep_tree(char **argv); +void *grep_malloc(size_t size); +void *grep_calloc(size_t nmemb, size_t size); +void *grep_realloc(void *ptr, size_t size); +char *grep_strdup(const char *str); +void printline(struct str *line, int sep, regmatch_t *matches, int m); + +/* queue.c */ +void enqueue(struct str *x); +void printqueue(void); +void clearqueue(void); + +/* file.c */ +void grep_close(struct file *f); +struct file *grep_open(const char *path); +char *grep_fgetln(struct file *f, size_t *len); + +/* fastgrep.c */ +int fastcomp(fastgrep_t *, const char *); +void fgrepcomp(fastgrep_t *, const char *); +int grep_search(fastgrep_t *, const unsigned char *, size_t, regmatch_t *); diff --git a/usr.bin/grep/nls/C.msg b/usr.bin/grep/nls/C.msg new file mode 100644 index 0000000..d07aea6 --- /dev/null +++ b/usr.bin/grep/nls/C.msg @@ -0,0 +1,13 @@ +$ $FreeBSD: head/usr.bin/grep/nls/C.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(standard input)" +2 "cannot read bzip2 compressed file" +3 "unknown %s option" +4 "usage: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n" +5 "\t[-e pattern] [-f file] [--binary-files=value] [--color=when]\n" +6 "\t[--context[=num]] [--directories=action] [--label] [--line-buffered]\n" +7 "\t[pattern] [file ...]\n" +8 "Binary file %s matches\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/es_ES.ISO8859-1.msg b/usr.bin/grep/nls/es_ES.ISO8859-1.msg new file mode 100644 index 0000000..441ba01 --- /dev/null +++ b/usr.bin/grep/nls/es_ES.ISO8859-1.msg @@ -0,0 +1,14 @@ +$ $NetBSD: es_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/es_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(entrada estándar)" +2 "no se puede leer el fichero comprimido bzip2" +3 "opción desconocida de %s" +4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n" +5 "\t[-e pauta] [-f fichero] [--binary-files=valor] [--color=cuando]\n" +6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n" +7 "\t[pauta] [fichero ...]\n" +8 "fichero binario %s se ajusta\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/gl_ES.ISO8859-1.msg b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg new file mode 100644 index 0000000..ab50cea --- /dev/null +++ b/usr.bin/grep/nls/gl_ES.ISO8859-1.msg @@ -0,0 +1,14 @@ +$ $NetBSD: gl_ES.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/gl_ES.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(entrada estándar)" +2 "non se pode ler o ficheiro comprimido bzip2" +3 "opción descoñecida de %s" +4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A no] [-B no] [-C[no]]\n" +5 "\t[-e pauta] [-f ficheiro] [--binary-files=valor] [--color=cando]\n" +6 "\t[--context[=no]] [--directories=acción] [--label] [--line-buffered]\n" +7 "\t[pauta] [ficheiro ...]\n" +8 "ficheiro binario %s conforma\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/hu_HU.ISO8859-2.msg b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg new file mode 100644 index 0000000..69dc2ec --- /dev/null +++ b/usr.bin/grep/nls/hu_HU.ISO8859-2.msg @@ -0,0 +1,14 @@ +$ $NetBSD: hu_HU.ISO8859-2.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/hu_HU.ISO8859-2.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(szabványos bemenet)" +2 "bzip2 tömörített fájl nem olvasható" +3 "ismeretlen %s opció" +4 "használat: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A szám] [-B szám] [-C[szám]]\n" +5 "\t[-e minta] [-f fájl] [--binary-files=érték] [--color=mikor]\n" +6 "\t[--context[=szám]] [--directories=mûvelet] [--label] [--line-buffered]\n" +7 "\t[minta] [fájl ...]\n" +8 "%s bináris fájl illeszkedik\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/ja_JP.SJIS.msg b/usr.bin/grep/nls/ja_JP.SJIS.msg new file mode 100644 index 0000000..99357b6 --- /dev/null +++ b/usr.bin/grep/nls/ja_JP.SJIS.msg @@ -0,0 +1,14 @@ +$ $NetBSD: ja_JP.SJIS.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.SJIS.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(•W€“ü—Í)" +2 "bzip2 ˆ³kƒtƒ@ƒCƒ‹‚ð“Ç‚Ýž‚Þ‚±‚Æ‚ª‚Å‚«‚Ü‚¹‚ñ" +3 "%s ƒIƒvƒVƒ‡ƒ“‚ÌŽw’è’l‚ÉŒë‚肪‚ ‚è‚Ü‚·" +4 "Žg‚¢•û: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ”Žš] [-B ”Žš] [-C[”Žš]]\n" +5 "\t[-e ƒpƒ^[ƒ“] [-f ƒtƒ@ƒCƒ‹–¼] [--binary-files=’l] [--color=’l]\n" +6 "\t[--context[=”Žš]] [--directories=“®ì] [--label] [--line-buffered]\n" +7 "\t[ƒpƒ^[ƒ“] [ƒtƒ@ƒCƒ‹–¼ ...]\n" +8 "ƒoƒCƒiƒŠƒtƒ@ƒCƒ‹ %s ‚Ƀ}ƒbƒ`‚µ‚Ü‚µ‚½\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/ja_JP.UTF-8.msg b/usr.bin/grep/nls/ja_JP.UTF-8.msg new file mode 100644 index 0000000..3d4aed3 --- /dev/null +++ b/usr.bin/grep/nls/ja_JP.UTF-8.msg @@ -0,0 +1,14 @@ +$ $NetBSD: ja_JP.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.UTF-8.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(標準入力)" +2 "bzip2 圧縮ファイルを読ã¿è¾¼ã‚€ã“ã¨ãŒã§ãã¾ã›ã‚“" +3 "%s オプションã®æŒ‡å®šå€¤ã«èª¤ã‚ŠãŒã‚ã‚Šã¾ã™" +4 "使ã„æ–¹: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A æ•°å­—] [-B æ•°å­—] [-C[æ•°å­—]]\n" +5 "\t[-e パターン] [-f ファイルå] [--binary-files=値] [--color=値]\n" +6 "\t[--context[=æ•°å­—]] [--directories=動作] [--label] [--line-buffered]\n" +7 "\t[パターン] [ファイルå ...]\n" +8 "ãƒã‚¤ãƒŠãƒªãƒ•ã‚¡ã‚¤ãƒ« %s ã«ãƒžãƒƒãƒã—ã¾ã—ãŸ\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/ja_JP.eucJP.msg b/usr.bin/grep/nls/ja_JP.eucJP.msg new file mode 100644 index 0000000..08e0e7d --- /dev/null +++ b/usr.bin/grep/nls/ja_JP.eucJP.msg @@ -0,0 +1,14 @@ +$ $NetBSD: ja_JP.eucJP.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/ja_JP.eucJP.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(ɸ½àÆþÎÏ)" +2 "bzip2 °µ½Ì¥Õ¥¡¥¤¥ë¤òÆɤ߹þ¤à¤³¤È¤¬¤Ç¤­¤Þ¤»¤ó" +3 "%s ¥ª¥×¥·¥ç¥ó¤Î»ØÄêÃͤ˸í¤ê¤¬¤¢¤ê¤Þ¤¹" +4 "»È¤¤Êý: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ¿ô»ú] [-B ¿ô»ú] [-C[¿ô»ú]]\n" +5 "\t[-e ¥Ñ¥¿¡¼¥ó] [-f ¥Õ¥¡¥¤¥ë̾] [--binary-files=ÃÍ] [--color=ÃÍ]\n" +6 "\t[--context[=¿ô»ú]] [--directories=Æ°ºî] [--label] [--line-buffered]\n" +7 "\t[¥Ñ¥¿¡¼¥ó] [¥Õ¥¡¥¤¥ë̾ ...]\n" +8 "¥Ð¥¤¥Ê¥ê¥Õ¥¡¥¤¥ë %s ¤Ë¥Þ¥Ã¥Á¤·¤Þ¤·¤¿\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/pt_BR.ISO8859-1.msg b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg new file mode 100644 index 0000000..7b6c20e --- /dev/null +++ b/usr.bin/grep/nls/pt_BR.ISO8859-1.msg @@ -0,0 +1,14 @@ +$ $NetBSD: pt_BR.ISO8859-1.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/pt_BR.ISO8859-1.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(entrada padrão)" +2 "não se posso ler o fichero comprimido bzip2" +3 "opcão não conhecida de %s" +4 "uso: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A num] [-B num] [-C[num]]\n" +5 "\t[-e padrão] [-f arquivo] [--binary-files=valor] [--color=quando]\n" +6 "\t[--context[=num]] [--directories=ação] [--label] [--line-buffered]\n" +7 "\t[padrão] [arquivo ...]\n" +8 "arquivo binário %s casa com o padrão\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/ru_RU.KOI8-R.msg b/usr.bin/grep/nls/ru_RU.KOI8-R.msg new file mode 100644 index 0000000..4c69f73 --- /dev/null +++ b/usr.bin/grep/nls/ru_RU.KOI8-R.msg @@ -0,0 +1,14 @@ +$ $NetBSD: ru_RU.KOI8-R.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/ru_RU.KOI8-R.msg 210622 2010-07-29 18:02:57Z gabor $ +$ +$set 1 +$quote " +1 "(ÓÔÁÎÄÁÒÔÎÙÊ ××ÏÄ)" +2 "ÎÅ ÍÏÇÕ ÐÒÏÞÉÔÁÔØ ÓÖÁÔÙÊ × bzip2 ÆÁÊÌ" +3 "ÎÅÉÚ×ÅÓÔÎÙÊ ËÌÀÞ %s" +4 "ÉÓÐÏÌØÚÏ×ÁÎÉÅ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A ÞÉÓ] [-B ÞÉÓ] [-C[ÞÉÓ]]\n" +5 "\t[-e ÛÁÂÌÏÎ] [-f ÆÁÊÌ] [--binary-files=ÚÎÁÞÅÎÉÅ] [--color=ËÏÇÄÁ]\n" +6 "\t[--context[=ÞÉÓ]] [--directories=ÄÅÊÓÔ×ÉÅ] [--label] [--line-buffered]\n" +7 "\t[ÛÁÂÌÏÎ] [ÆÁÊÌ ...]\n" +8 "Ä×ÏÉÞÎÙÊ ÆÁÊÌ %s ÓÏ×ÐÁÄÁÅÔ\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/uk_UA.UTF-8.msg b/usr.bin/grep/nls/uk_UA.UTF-8.msg new file mode 100644 index 0000000..e6b9f54 --- /dev/null +++ b/usr.bin/grep/nls/uk_UA.UTF-8.msg @@ -0,0 +1,13 @@ +$ $NetBSD: uk_UA.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/uk_UA.UTF-8.msg 210927 2010-08-06 10:34:48Z gabor $ +$set 1 +$quote " +1 "(Ñтандартний ввід)" +2 "не можу прочитати ÑтиÑнутий bzip2 файл" +3 "невiдома Ð¾Ð¿Ñ†Ñ–Ñ %s" +4 "викориÑтаннÑ: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A чиÑ] [-B чиÑ] [-C[чиÑ]]\n" +5 "\t[-e шаблон] [-f файл] [--binary-files=значеннÑ] [--color=коли]\n" +6 "\t[--context[=чиÑ] [--directories=діÑ] [--label] [--line-buffered]\n" +7 "\t[шаблон] [файл ...]\n" +8 "двійковий файл %s Ñпівпадає\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/nls/zh_CN.UTF-8.msg b/usr.bin/grep/nls/zh_CN.UTF-8.msg new file mode 100644 index 0000000..e9d40cd --- /dev/null +++ b/usr.bin/grep/nls/zh_CN.UTF-8.msg @@ -0,0 +1,14 @@ +$ $NetBSD: zh_CN.UTF-8.msg,v 1.2 2011/04/18 22:46:48 joerg Exp $ +$ $FreeBSD: head/usr.bin/grep/nls/zh_CN.UTF-8.msg 212927 2010-09-20 19:42:52Z delphij $ +$ +$set 1 +$quote " +1 "(标准输入)" +2 "è¯»å– bzip2 压缩文件时出错" +3 "选项 %s 无法识别" +4 "用法: %s [-abcDEFGHhIiJLlmnOoPqRSsUVvwxZz] [-A 行数] [-B 行数] [-C[行数]]\n" +5 "\t[-e 模å¼] [-f 文件] [--binary-files=值] [--color=何时]\n" +6 "\t[--context[=行数]] [--directories=动作] [--label] [--line-buffered]\n" +7 "\t[模å¼] [文件å ...]\n" +8 "二进制文件 %s 包å«æ¨¡å¼\n" +9 "%s (BSD grep) %s\n" diff --git a/usr.bin/grep/queue.c b/usr.bin/grep/queue.c new file mode 100644 index 0000000..e3c6be1 --- /dev/null +++ b/usr.bin/grep/queue.c @@ -0,0 +1,116 @@ +/* $NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $ */ +/* $FreeBSD: head/usr.bin/grep/queue.c 211496 2010-08-19 09:28:59Z des $ */ +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * A really poor man's queue. It does only what it has to and gets out of + * Dodge. It is used in place of to get a better performance. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: queue.c,v 1.5 2011/08/31 16:24:57 plunky Exp $"); + +#include +#include + +#include +#include + +#include "grep.h" + +struct qentry { + STAILQ_ENTRY(qentry) list; + struct str data; +}; + +static STAILQ_HEAD(, qentry) queue = STAILQ_HEAD_INITIALIZER(queue); +static unsigned long long count; + +static struct qentry *dequeue(void); + +void +enqueue(struct str *x) +{ + struct qentry *item; + + item = grep_malloc(sizeof(struct qentry)); + item->data.dat = grep_malloc(sizeof(char) * x->len); + item->data.len = x->len; + item->data.line_no = x->line_no; + item->data.off = x->off; + memcpy(item->data.dat, x->dat, x->len); + item->data.file = x->file; + + STAILQ_INSERT_TAIL(&queue, item, list); + + if (++count > Bflag) { + item = dequeue(); + free(item->data.dat); + free(item); + } +} + +static struct qentry * +dequeue(void) +{ + struct qentry *item; + + item = STAILQ_FIRST(&queue); + if (item == NULL) + return (NULL); + + STAILQ_REMOVE_HEAD(&queue, list); + --count; + return (item); +} + +void +printqueue(void) +{ + struct qentry *item; + + while ((item = dequeue()) != NULL) { + printline(&item->data, '-', NULL, 0); + free(item->data.dat); + free(item); + } +} + +void +clearqueue(void) +{ + struct qentry *item; + + while ((item = dequeue()) != NULL) { + free(item->data.dat); + free(item); + } +} diff --git a/usr.bin/grep/util.c b/usr.bin/grep/util.c new file mode 100644 index 0000000..a3c9e4c --- /dev/null +++ b/usr.bin/grep/util.c @@ -0,0 +1,500 @@ +/* $NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $ */ +/* $FreeBSD: head/usr.bin/grep/util.c 211496 2010-08-19 09:28:59Z des $ */ +/* $OpenBSD: util.c,v 1.39 2010/07/02 22:18:03 tedu Exp $ */ + +/*- + * Copyright (c) 1999 James Howard and Dag-Erling Coïdan Smørgrav + * Copyright (C) 2008-2010 Gabor Kovesdan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: util.c,v 1.19 2018/02/05 22:14:26 mrg Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "grep.h" + +static bool first, first_global = true; +static unsigned long long since_printed; + +static int procline(struct str *l, int); + +bool +file_matching(const char *fname) +{ + char *fname_base, *fname_copy; + unsigned int i; + bool ret; + + ret = finclude ? false : true; + fname_copy = grep_strdup(fname); + fname_base = basename(fname_copy); + + for (i = 0; i < fpatterns; ++i) { + if (fnmatch(fpattern[i].pat, fname, 0) == 0 || + fnmatch(fpattern[i].pat, fname_base, 0) == 0) { + if (fpattern[i].mode == EXCL_PAT) { + free(fname_copy); + return (false); + } else + ret = true; + } + } + free(fname_copy); + return (ret); +} + +static inline bool +dir_matching(const char *dname) +{ + unsigned int i; + bool ret; + + ret = dinclude ? false : true; + + for (i = 0; i < dpatterns; ++i) { + if (dname != NULL && + fnmatch(dname, dpattern[i].pat, 0) == 0) { + if (dpattern[i].mode == EXCL_PAT) + return (false); + else + ret = true; + } + } + return (ret); +} + +/* + * Processes a directory when a recursive search is performed with + * the -R option. Each appropriate file is passed to procfile(). + */ +int +grep_tree(char **argv) +{ + FTS *fts; + FTSENT *p; + char *d, *dir = NULL; + int c, fts_flags; + bool ok; + + c = fts_flags = 0; + + switch(linkbehave) { + case LINK_EXPLICIT: + fts_flags = FTS_COMFOLLOW; + break; + case LINK_SKIP: + fts_flags = FTS_PHYSICAL; + break; + default: + fts_flags = FTS_LOGICAL; + + } + + fts_flags |= FTS_NOSTAT | FTS_NOCHDIR; + + if (!(fts = fts_open(argv, fts_flags, NULL))) + err(2, "fts_open"); + while ((p = fts_read(fts)) != NULL) { + switch (p->fts_info) { + case FTS_DNR: + /* FALLTHROUGH */ + case FTS_ERR: + errx(2, "%s: %s", p->fts_path, strerror(p->fts_errno)); + break; + case FTS_D: + /* FALLTHROUGH */ + case FTS_DP: + break; + case FTS_DC: + /* Print a warning for recursive directory loop */ + warnx("warning: %s: recursive directory loop", + p->fts_path); + break; + default: + /* Check for file exclusion/inclusion */ + ok = true; + if (dexclude || dinclude) { + if ((d = strrchr(p->fts_path, '/')) != NULL) { + dir = grep_malloc(sizeof(char) * + (d - p->fts_path + 1)); + memcpy(dir, p->fts_path, + d - p->fts_path); + dir[d - p->fts_path] = '\0'; + } + ok = dir_matching(dir); + free(dir); + dir = NULL; + } + if (fexclude || finclude) + ok &= file_matching(p->fts_path); + + if (ok) + c += procfile(p->fts_path); + break; + } + } + + fts_close(fts); + return (c); +} + +/* + * Opens a file and processes it. Each file is processed line-by-line + * passing the lines to procline(). + */ +int +procfile(const char *fn) +{ + struct file *f; + struct stat sb; + struct str ln; + mode_t s; + int c, t; + + if (mflag && (mcount <= 0)) + return (0); + + if (strcmp(fn, "-") == 0) { + fn = label != NULL ? label : getstr(1); + f = grep_open(NULL); + } else { + if (!stat(fn, &sb)) { + /* Check if we need to process the file */ + s = sb.st_mode & S_IFMT; + if (s == S_IFDIR && dirbehave == DIR_SKIP) + return (0); + if ((s == S_IFIFO || s == S_IFCHR || s == S_IFBLK + || s == S_IFSOCK) && devbehave == DEV_SKIP) + return (0); + } + f = grep_open(fn); + } + if (f == NULL) { + if (!sflag) + warn("%s", fn); + if (errno == ENOENT) + notfound = true; + return (0); + } + + ln.file = grep_malloc(strlen(fn) + 1); + strcpy(ln.file, fn); + ln.line_no = 0; + ln.len = 0; + tail = 0; + ln.off = -1; + + for (first = true, c = 0; c == 0 || !(lflag || qflag); ) { + ln.off += ln.len + 1; + if ((ln.dat = grep_fgetln(f, &ln.len)) == NULL || ln.len == 0) + break; + if (ln.len > 0 && ln.dat[ln.len - 1] == line_sep) + --ln.len; + ln.line_no++; + + /* Return if we need to skip a binary file */ + if (f->binary && binbehave == BINFILE_SKIP) { + grep_close(f); + free(ln.file); + free(f); + return (0); + } + /* Process the file line-by-line */ + t = procline(&ln, f->binary); + c += t; + + /* Count the matches if we have a match limit */ + if (mflag) { + mcount -= t; + if (mcount <= 0) + break; + } + } + if (Bflag > 0) + clearqueue(); + grep_close(f); + + if (cflag) { + if (!hflag) + printf("%s:", ln.file); + printf("%u%c", c, line_sep); + } + if (lflag && !qflag && c != 0) + printf("%s%c", fn, line_sep); + if (Lflag && !qflag && c == 0) + printf("%s%c", fn, line_sep); + if (c && !cflag && !lflag && !Lflag && + binbehave == BINFILE_BIN && f->binary && !qflag) + printf(getstr(8), fn); + + free(ln.file); + free(f); + return (c); +} + +#define iswword(x) (iswalnum((x)) || (x) == L'_') + +/* + * Processes a line comparing it with the specified patterns. Each pattern + * is looped to be compared along with the full string, saving each and every + * match, which is necessary to colorize the output and to count the + * matches. The matching lines are passed to printline() to display the + * appropriate output. + */ +static int +procline(struct str *l, int nottext) +{ + regmatch_t matches[MAX_LINE_MATCHES]; + regmatch_t pmatch; + size_t st = 0; + unsigned int i; + int c = 0, m = 0, r = 0; + + /* Loop to process the whole line */ + while (st <= l->len) { + pmatch.rm_so = st; + pmatch.rm_eo = l->len; + + /* Loop to compare with all the patterns */ + for (i = 0; i < patterns; i++) { +/* + * XXX: grep_search() is a workaround for speed up and should be + * removed in the future. See fastgrep.c. + */ + if (fg_pattern[i].pattern) { + r = grep_search(&fg_pattern[i], + (unsigned char *)l->dat, + l->len, &pmatch); + r = (r == 0) ? 0 : REG_NOMATCH; + st = pmatch.rm_eo; + } else { + r = regexec(&r_pattern[i], l->dat, 1, + &pmatch, eflags); + r = (r == 0) ? 0 : REG_NOMATCH; + st = pmatch.rm_eo; + } + if (r == REG_NOMATCH) + continue; + /* Check for full match */ + if (xflag && + (pmatch.rm_so != 0 || + (size_t)pmatch.rm_eo != l->len)) + continue; + /* Check for whole word match */ + if (fg_pattern[i].word && pmatch.rm_so != 0) { + wchar_t wbegin, wend; + + wbegin = wend = L' '; + if (pmatch.rm_so != 0 && + sscanf(&l->dat[pmatch.rm_so - 1], + "%lc", &wbegin) != 1) + continue; + if ((size_t)pmatch.rm_eo != l->len && + sscanf(&l->dat[pmatch.rm_eo], + "%lc", &wend) != 1) + continue; + if (iswword(wbegin) || iswword(wend)) + continue; + } + c = 1; + if (m < MAX_LINE_MATCHES) + matches[m++] = pmatch; + /* matches - skip further patterns */ + if ((color != NULL && !oflag) || qflag || lflag) + break; + } + + if (vflag) { + c = !c; + break; + } + /* One pass if we are not recording matches */ + if ((color != NULL && !oflag) || qflag || lflag) + break; + + if (st == (size_t)pmatch.rm_so) + break; /* No matches */ + } + + if (c && binbehave == BINFILE_BIN && nottext) + return (c); /* Binary file */ + + /* Dealing with the context */ + if ((tail || c) && !cflag && !qflag && !lflag && !Lflag) { + if (c) { + if ((Aflag || Bflag) && !first_global && + (first || since_printed > Bflag)) + printf("--\n"); + tail = Aflag; + if (Bflag > 0) + printqueue(); + printline(l, ':', matches, m); + } else { + printline(l, '-', matches, m); + tail--; + } + first = false; + first_global = false; + since_printed = 0; + } else { + if (Bflag) + enqueue(l); + since_printed++; + } + return (c); +} + +/* + * Safe malloc() for internal use. + */ +void * +grep_malloc(size_t size) +{ + void *ptr; + + if ((ptr = malloc(size)) == NULL) + err(2, "malloc"); + return (ptr); +} + +/* + * Safe calloc() for internal use. + */ +void * +grep_calloc(size_t nmemb, size_t size) +{ + void *ptr; + + if ((ptr = calloc(nmemb, size)) == NULL) + err(2, "calloc"); + return (ptr); +} + +/* + * Safe realloc() for internal use. + */ +void * +grep_realloc(void *ptr, size_t size) +{ + + if ((ptr = realloc(ptr, size)) == NULL) + err(2, "realloc"); + return (ptr); +} + +/* + * Safe strdup() for internal use. + */ +char * +grep_strdup(const char *str) +{ + char *ret; + + if ((ret = strdup(str)) == NULL) + err(2, "strdup"); + return (ret); +} + +/* + * Prints a matching line according to the command line options. + */ +void +printline(struct str *line, int sep, regmatch_t *matches, int m) +{ + size_t a = 0; + int i, n = 0; + + if (!hflag) { + if (nullflag == 0) + fputs(line->file, stdout); + else { + printf("%s", line->file); + putchar(0); + } + ++n; + } + if (nflag) { + if (n > 0) + putchar(sep); + printf("%d", line->line_no); + ++n; + } + if (bflag) { + if (n > 0) + putchar(sep); + printf("%lld", (long long)line->off); + ++n; + } + if (n) + putchar(sep); + /* --color and -o */ + if ((oflag || color) && m > 0) { + for (i = 0; i < m; i++) { + if (!oflag) + fwrite(line->dat + a, matches[i].rm_so - a, 1, + stdout); + if (color) + fprintf(stdout, "\33[%sm\33[K", color); + + fwrite(line->dat + matches[i].rm_so, + matches[i].rm_eo - matches[i].rm_so, 1, + stdout); + + if (color) + fprintf(stdout, "\33[m\33[K"); + a = matches[i].rm_eo; + if (oflag) + putchar('\n'); + } + if (!oflag) { + if (line->len - a > 0) + fwrite(line->dat + a, line->len - a, 1, stdout); + putchar(line_sep); + } + } else { + fwrite(line->dat, line->len, 1, stdout); + putchar(line_sep); + } +} diff --git a/usr.bin/head/head.1 b/usr.bin/head/head.1 new file mode 100644 index 0000000..a7fa0bc --- /dev/null +++ b/usr.bin/head/head.1 @@ -0,0 +1,95 @@ +.\" $NetBSD: head.1,v 1.14 2017/07/04 06:59:34 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)head.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd May 4, 2004 +.Dt HEAD 1 +.Os +.Sh NAME +.Nm head +.Nd display first lines of a file +.Sh SYNOPSIS +.Nm +.Op Fl qv +.Op Fl n Ar count +.Op Fl c Ar byte_count +.Op Ar file ... +.Sh DESCRIPTION +This filter displays the first +.Ar count +lines of each of the specified files, or of the standard input if no +files are specified. +If +.Ar count +is omitted it defaults to 10. +If +.Fl c Ar byte_count +is specified, +.Nm +counts bytes instead of lines. +.Pp +If more than a single file is specified, or the +.Fl v +option is used, each file is preceded by a header consisting of the string +.Dq ==> XXX \*[Le]= +where +.Dq XXX +is the name of the file. +The +.Fl q +flag disables the printing of the header in all cases. +.Sh EXIT STATUS +.Ex -std head +.Sh COMPATIBILITY +The historic command line syntax of +.Nm +is supported by this implementation. +.Pp +This command is mostly compatible with GNU extensions to +.Nm . +.Sh SEE ALSO +.Xr tail 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.Sh HISTORY +The +.Nm +utility appeared in +.Bx 3.0 . +It was enhanced to include the +.Fl c , +.Fl q , +and +.Fl v +options for +.Nx 2.1 . diff --git a/usr.bin/head/head.c b/usr.bin/head/head.c new file mode 100644 index 0000000..1d24d48 --- /dev/null +++ b/usr.bin/head/head.c @@ -0,0 +1,204 @@ +/* $NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $ */ + +/* + * Copyright (c) 1980, 1987, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)head.c 8.2 (Berkeley) 5/4/95"; +#else +__RCSID("$NetBSD: head.c,v 1.24 2016/05/12 01:56:44 nonaka Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * head - give the first few lines of a stream or of each of a set of files + * + * Bill Joy UCB August 24, 1977 + */ + +static void head(FILE *, intmax_t, intmax_t); +static void obsolete(char *[]); +__dead static void usage(void); + + +int +main(int argc, char *argv[]) +{ + int ch; + FILE *fp; + int first; + intmax_t linecnt; + intmax_t bytecnt; + char *ep; + int eval = 0; + int qflag = 0; + int vflag = 0; + + (void)setlocale(LC_ALL, ""); + obsolete(argv); + linecnt = 10; + bytecnt = 0; + while ((ch = getopt(argc, argv, "c:n:qv")) != -1) + switch(ch) { + case 'c': + errno = 0; + bytecnt = strtoimax(optarg, &ep, 10); + if ((bytecnt == INTMAX_MAX && errno == ERANGE) || + *ep || bytecnt <= 0) + errx(1, "illegal byte count -- %s", optarg); + break; + + case 'n': + errno = 0; + linecnt = strtoimax(optarg, &ep, 10); + if ((linecnt == INTMAX_MAX && errno == ERANGE) || + *ep || linecnt <= 0) + errx(1, "illegal line count -- %s", optarg); + break; + + case 'q': + qflag = 1; + vflag = 0; + break; + + case 'v': + qflag = 0; + vflag = 1; + break; + + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (*argv) + for (first = 1; *argv; ++argv) { + if ((fp = fopen(*argv, "r")) == NULL) { + warn("%s", *argv); + eval = 1; + continue; + } + if (vflag || (qflag == 0 && argc > 1)) { + (void)printf("%s==> %s <==\n", + first ? "" : "\n", *argv); + first = 0; + } + head(fp, linecnt, bytecnt); + (void)fclose(fp); + } + else + head(stdin, linecnt, bytecnt); + exit(eval); +} + +static void +head(FILE *fp, intmax_t cnt, intmax_t bytecnt) +{ + char buf[65536]; + size_t len, rv, rv2; + int ch; + + if (bytecnt) { + while (bytecnt) { + len = sizeof(buf); + if (bytecnt > (intmax_t)sizeof(buf)) + len = sizeof(buf); + else + len = bytecnt; + rv = fread(buf, 1, len, fp); + if (rv == 0) + break; /* Distinguish EOF and error? */ + rv2 = fwrite(buf, 1, rv, stdout); + if (rv2 != rv) { + if (feof(stdout)) + errx(1, "EOF on stdout"); + else + err(1, "failure writing to stdout"); + } + bytecnt -= rv; + } + } else { + while ((ch = getc(fp)) != EOF) { + if (putchar(ch) == EOF) + err(1, "stdout"); + if (ch == '\n' && --cnt == 0) + break; + } + } +} + +static void +obsolete(char *argv[]) +{ + char *ap; + + while ((ap = *++argv)) { + /* Return if "--" or not "-[0-9]*". */ + if (ap[0] != '-' || ap[1] == '-' || + !isdigit((unsigned char)ap[1])) + return; + if ((ap = malloc(strlen(*argv) + 2)) == NULL) + err(1, NULL); + ap[0] = '-'; + ap[1] = 'n'; + (void)strcpy(ap + 2, *argv + 1); + *argv = ap; + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: %s [-n lines] [file ...]\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/iconv/iconv.1 b/usr.bin/iconv/iconv.1 new file mode 100644 index 0000000..69a4dcc --- /dev/null +++ b/usr.bin/iconv/iconv.1 @@ -0,0 +1,122 @@ +.\" $NetBSD: iconv.1,v 1.5 2013/05/12 12:25:12 rodent Exp $ +.\" +.\" Copyright (c)2003 Citrus Project, +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd March 20, 2008 +.Dt ICONV 1 +.Os +.\" ---------------------------------------------------------------------- +.Sh NAME +.Nm iconv +.Nd codeset conversion utility +.\" ---------------------------------------------------------------------- +.Sh SYNOPSIS +.Nm +.Op Fl cs +.Fl f +.Ar from_name +.Fl t +.Ar to_name +.Op Ar file ... +.Nm +.Fl f +.Ar from_name +.Op Fl cs +.Op Fl t Ar to_name +.Op Ar file ... +.Nm +.Fl t +.Ar to_name +.Op Fl cs +.Op Fl f Ar from_name +.Op Ar file ... +.Nm +.Fl l +.\" ---------------------------------------------------------------------- +.Sh DESCRIPTION +The +.Nm +utility converts the codeset of +.Ar file +(or from standard input if no file is specified) from codeset +.Ar from_name +to codeset +.Ar to_name +and outputs the +converted text on standard output. +.Pp +The following options are available: +.Bl -tag -width 0123 +.It Fl c +Prevent output of any invalid characters. +By default, +.Nm +outputs an +.Dq invalid character +specified by the +.Ar to_name +codeset when it encounters a character which is valid in the +.Ar from_name +codeset but does not have a corresponding character in the +.Ar to_name +codeset. +.It Fl f +Specifies the source codeset name as +.Ar from_name . +.It Fl l +Lists available codeset names. +Note that not all combinations of +.Ar from_name +and +.Ar to_name +are valid. +.It Fl s +Silent. +By default, +.Nm +outputs the number of +.Dq invalid characters +to standard error if they exist. +This option prevents this behaviour. +.It Fl t +Specifies the destination codeset name as +.Ar to_name . +.El +.\" ---------------------------------------------------------------------- +.Sh EXIT STATUS +.Ex -std iconv +.\" ---------------------------------------------------------------------- +.Sh SEE ALSO +.Xr iconv 3 +.\" ---------------------------------------------------------------------- +.Sh STANDARDS +.Nm +conforms to +.St -p1003.1-2001 . +.\" ---------------------------------------------------------------------- +.Sh HISTORY +.Nm +first appeared in +.Nx 2.0 . diff --git a/usr.bin/iconv/iconv.c b/usr.bin/iconv/iconv.c new file mode 100644 index 0000000..dd9a5d4 --- /dev/null +++ b/usr.bin/iconv/iconv.c @@ -0,0 +1,236 @@ +/* $NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $ */ + +/*- + * Copyright (c)2003 Citrus Project, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: iconv.c,v 1.19 2013/10/07 02:00:46 dholland Exp $"); +#endif /* LIBC_SCCS and not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) __dead; +static int scmp(const void *, const void *); +static void show_codesets(void); +static void do_conv(const char *, FILE *, const char *, const char *, int, int); + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage:\t%1$s [-cs] -f -t [file ...]\n" + "\t%1$s -f [-cs] [-t ] [file ...]\n" + "\t%1$s -t [-cs] [-f ] [file ...]\n" + "\t%1$s -l\n", getprogname()); + exit(1); +} + +/* + * qsort() helper function + */ +static int +scmp(const void *v1, const void *v2) +{ + const char * const *s1 = v1; + const char * const *s2 = v2; + + return strcasecmp(*s1, *s2); +} + +static void +show_codesets(void) +{ + char **list; + size_t sz, i; + + if (__iconv_get_list(&list, &sz)) + err(EXIT_FAILURE, "__iconv_get_list()"); + + qsort(list, sz, sizeof(char *), scmp); + + for (i = 0; i < sz; i++) + (void)printf("%s\n", list[i]); + + __iconv_free_list(list, sz); +} + +#define INBUFSIZE 1024 +#define OUTBUFSIZE (INBUFSIZE * 2) +/*ARGSUSED*/ +static void +do_conv(const char *fn, FILE *fp, const char *from, const char *to, int silent, + int hide_invalid) +{ + char inbuf[INBUFSIZE], outbuf[OUTBUFSIZE], *out; + const char *in; + size_t inbytes, outbytes, ret, invalids; + iconv_t cd; + uint32_t flags = 0; + int serrno; + + if (hide_invalid) + flags |= __ICONV_F_HIDE_INVALID; + cd = iconv_open(to, from); + if (cd == (iconv_t)-1) + err(EXIT_FAILURE, "iconv_open(%s, %s)", to, from); + + invalids = 0; + while ((inbytes = fread(inbuf, 1, INBUFSIZE, fp)) > 0) { + in = inbuf; + while (inbytes > 0) { + size_t inval; + + out = outbuf; + outbytes = OUTBUFSIZE; + ret = __iconv(cd, &in, &inbytes, &out, &outbytes, + flags, &inval); + serrno = errno; + invalids += inval; + if (outbytes < OUTBUFSIZE) + (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, + stdout); + errno = serrno; + if (ret == (size_t)-1 && errno != E2BIG) { + /* + * XXX: iconv(3) is bad interface. + * invalid character count is lost here. + * instead, we just provide __iconv function. + */ + if (errno != EINVAL || in == inbuf) + err(EXIT_FAILURE, "iconv()"); + + /* incomplete input character */ + (void)memmove(inbuf, in, inbytes); + ret = fread(inbuf + inbytes, 1, + INBUFSIZE - inbytes, fp); + if (ret == 0) { + fflush(stdout); + if (feof(fp)) + errx(EXIT_FAILURE, + "unexpected end of file; " + "the last character is " + "incomplete."); + else + err(EXIT_FAILURE, "fread()"); + } + in = inbuf; + inbytes += ret; + } + } + } + /* reset the shift state of the output buffer */ + outbytes = OUTBUFSIZE; + out = outbuf; + ret = iconv(cd, NULL, NULL, &out, &outbytes); + if (ret == (size_t)-1) + err(EXIT_FAILURE, "iconv()"); + if (outbytes < OUTBUFSIZE) + (void)fwrite(outbuf, 1, OUTBUFSIZE - outbytes, stdout); + + if (invalids > 0 && !silent) + warnx("warning: invalid characters: %lu", + (unsigned long)invalids); + + iconv_close(cd); +} + +int +main(int argc, char **argv) +{ + int ch, i; + int opt_l = 0, opt_s = 0, opt_c = 0; + char *opt_f = NULL, *opt_t = NULL; + FILE *fp; + + setlocale(LC_ALL, ""); + setprogname(argv[0]); + + while ((ch = getopt(argc, argv, "cslf:t:")) != EOF) { + switch (ch) { + case 'c': + opt_c = 1; + break; + case 's': + opt_s = 1; + break; + case 'l': + /* list */ + opt_l = 1; + break; + case 'f': + /* from */ + opt_f = estrdup(optarg); + break; + case 't': + /* to */ + opt_t = estrdup(optarg); + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + if (opt_l) { + if (argc > 0 || opt_s || opt_f != NULL || opt_t != NULL) { + warnx("-l is not allowed with other flags."); + usage(); + } + show_codesets(); + } else { + if (opt_f == NULL) { + if (opt_t == NULL) + usage(); + opt_f = nl_langinfo(CODESET); + } else if (opt_t == NULL) + opt_t = nl_langinfo(CODESET); + + if (argc == 0) + do_conv("", stdin, opt_f, opt_t, opt_s, opt_c); + else { + for (i = 0; i < argc; i++) { + fp = fopen(argv[i], "r"); + if (fp == NULL) + err(EXIT_FAILURE, "Cannot open `%s'", + argv[i]); + do_conv(argv[i], fp, opt_f, opt_t, opt_s, + opt_c); + (void)fclose(fp); + } + } + } + return EXIT_SUCCESS; +} diff --git a/usr.bin/id/groups.1 b/usr.bin/id/groups.1 new file mode 100644 index 0000000..684f0d5 --- /dev/null +++ b/usr.bin/id/groups.1 @@ -0,0 +1,59 @@ +.\" $NetBSD: groups.1,v 1.11 2017/07/04 07:00:30 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)groups.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt GROUPS 1 +.Os +.Sh NAME +.Nm groups +.Nd show group memberships +.Sh SYNOPSIS +.Nm +.Op Ar user +.Sh DESCRIPTION +The +.Nm +utility has been obsoleted by the +.Xr id 1 +utility, and is equivalent to +.Dq Nm id Fl Gn Op Ar user . +The command +.Dq Nm id Fl p +is suggested for normal interactive use. +.Pp +The +.Nm +utility displays the groups to which you (or the optionally specified user) +belong. +.Sh EXIT STATUS +.Ex -std groups +.Sh SEE ALSO +.Xr id 1 diff --git a/usr.bin/id/id.1 b/usr.bin/id/id.1 new file mode 100644 index 0000000..ad66b62 --- /dev/null +++ b/usr.bin/id/id.1 @@ -0,0 +1,136 @@ +.\" $NetBSD: id.1,v 1.14 2017/07/04 07:01:07 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)id.1 8.2 (Berkeley) 5/5/94 +.\" +.Dd May 5, 1994 +.Dt ID 1 +.Os +.Sh NAME +.Nm id +.Nd return user identity +.Sh SYNOPSIS +.Nm +.Op Ar user +.Nm +.Fl G Op Fl n +.Op Ar user +.Nm +.Fl g Op Fl nr +.Op Ar user +.Nm +.Fl p +.Op Ar user +.Nm +.Fl u Op Fl nr +.Op Ar user +.Sh DESCRIPTION +The +.Nm +utility displays the user and group names and numeric IDs, of the +calling process, to the standard output. +If the real and effective IDs are different, both are displayed, +otherwise only the real ID is displayed. +.Pp +If a +.Ar user +(login name or user ID) +is specified, the user and group IDs of that user are displayed. +In this case, the real and effective IDs are assumed to be the same. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl G +Display the different group IDs (effective, real and supplementary) +as white-space separated numbers, in no particular order. +.It Fl g +Display the effective group ID as a number. +.It Fl n +Display the name of the user or group ID for the +.Fl G , +.Fl g +and +.Fl u +options instead of the number. +If any of the ID numbers cannot be mapped into names, the number will be +displayed as usual. +.It Fl p +Make the output human-readable. +If the user name returned by +.Xr getlogin 2 +is different from the login name referenced by the user ID, the name +returned by +.Xr getlogin 2 +is displayed, preceded by the keyword ``login''. +The user ID as a name is displayed, preceded by the keyword ``uid''. +If the effective user ID is different from the real user ID, the real user +ID is displayed as a name, preceded by the keyword ``euid''. +If the effective group ID is different from the real group ID, the real group +ID is displayed as a name, preceded by the keyword ``rgid''. +The list of groups to which the user belongs is then displayed as names, +preceded by the keyword ``groups''. +Each display is on a separate line. +.It Fl r +Display the real ID for the +.Fl g +and +.Fl u +options instead of the effective ID. +.It Fl u +Display the effective user ID as a number. +.El +.Sh EXIT STATUS +.Ex -std id +.Sh SEE ALSO +.Xr who 1 +.Sh STANDARDS +The +.Nm +function is expected to conform to +.St -p1003.2 . +.Sh HISTORY +The +historic +.Xr groups 1 +command is equivalent to +.Dq Nm id Fl Gn Op Ar user . +.Pp +The +historic +.Xr whoami 1 +command is equivalent to +.Dq Nm id Fl un . +.Pp +The +.Nm +command first appeared in +.Bx 4.4 . diff --git a/usr.bin/id/id.c b/usr.bin/id/id.c new file mode 100644 index 0000000..720cc49 --- /dev/null +++ b/usr.bin/id/id.c @@ -0,0 +1,370 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)id.c 8.3 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: id.c,v 1.32 2011/09/16 15:39:26 joerg Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static void current(void); +static void pretty(struct passwd *); +static void group(struct passwd *, int); +__dead static void usage(void); +static void user(struct passwd *); +static struct passwd *who(char *); + +static int maxgroups; +static gid_t *groups; + +int +main(int argc, char *argv[]) +{ + struct group *gr; + struct passwd *pw; + int ch, id; + int Gflag, gflag, nflag, pflag, rflag, uflag; + const char *opts; + + Gflag = gflag = nflag = pflag = rflag = uflag = 0; + + if (strcmp(getprogname(), "groups") == 0) { + Gflag = 1; + nflag = 1; + opts = ""; + if (argc > 2) + usage(); + } else if (strcmp(getprogname(), "whoami") == 0) { + uflag = 1; + nflag = 1; + opts = ""; + if (argc > 1) + usage(); + } else + opts = "Ggnpru"; + + while ((ch = getopt(argc, argv, opts)) != -1) + switch (ch) { + case 'G': + Gflag = 1; + break; + case 'g': + gflag = 1; + break; + case 'n': + nflag = 1; + break; + case 'p': + pflag = 1; + break; + case 'r': + rflag = 1; + break; + case 'u': + uflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + switch (Gflag + gflag + pflag + uflag) { + case 1: + break; + case 0: + if (!nflag && !rflag) + break; + /* FALLTHROUGH */ + default: + usage(); + } + + if (strcmp(opts, "") != 0 && argc > 1) + usage(); + + pw = *argv ? who(*argv) : NULL; + + maxgroups = sysconf(_SC_NGROUPS_MAX); + if ((groups = malloc((maxgroups + 1) * sizeof(gid_t))) == NULL) + err(1, NULL); + + if (gflag) { + id = pw ? pw->pw_gid : rflag ? getgid() : getegid(); + if (nflag && (gr = getgrgid(id))) + (void)printf("%s\n", gr->gr_name); + else + (void)printf("%u\n", id); + goto done; + } + + if (uflag) { + id = pw ? pw->pw_uid : rflag ? getuid() : geteuid(); + if (nflag && (pw = getpwuid(id))) + (void)printf("%s\n", pw->pw_name); + else + (void)printf("%u\n", id); + goto done; + } + + if (Gflag) { + group(pw, nflag); + goto done; + } + + if (pflag) { + pretty(pw); + goto done; + } + + if (pw) + user(pw); + else + current(); +done: + free(groups); + + return 0; +} + +static void +pretty(struct passwd *pw) +{ + struct group *gr; + u_int eid, rid; + char *login; + + if (pw) { + (void)printf("uid\t%s\n", pw->pw_name); + (void)printf("groups\t"); + group(pw, 1); + } else { + if ((login = getlogin()) == NULL) + err(1, "getlogin"); + + pw = getpwuid(rid = getuid()); + if (pw == NULL || strcmp(login, pw->pw_name)) + (void)printf("login\t%s\n", login); + if (pw) + (void)printf("uid\t%s\n", pw->pw_name); + else + (void)printf("uid\t%u\n", rid); + + if ((eid = geteuid()) != rid) { + if ((pw = getpwuid(eid)) != NULL) + (void)printf("euid\t%s\n", pw->pw_name); + else + (void)printf("euid\t%u\n", eid); + } + if ((rid = getgid()) != (eid = getegid())) { + if ((gr = getgrgid(rid)) != NULL) + (void)printf("rgid\t%s\n", gr->gr_name); + else + (void)printf("rgid\t%u\n", rid); + } + (void)printf("groups\t"); + group(NULL, 1); + } +} + +static void +current(void) +{ + struct group *gr; + struct passwd *pw; + gid_t gid, egid, lastid; + uid_t uid, euid; + int cnt, ngroups; + const char *fmt; + + uid = getuid(); + (void)printf("uid=%ju", (uintmax_t)uid); + if ((pw = getpwuid(uid)) != NULL) + (void)printf("(%s)", pw->pw_name); + gid = getgid(); + (void)printf(" gid=%ju", (uintmax_t)gid); + if ((gr = getgrgid(gid)) != NULL) + (void)printf("(%s)", gr->gr_name); + if ((euid = geteuid()) != uid) { + (void)printf(" euid=%ju", (uintmax_t)euid); + if ((pw = getpwuid(euid)) != NULL) + (void)printf("(%s)", pw->pw_name); + } + if ((egid = getegid()) != gid) { + (void)printf(" egid=%ju", (uintmax_t)egid); + if ((gr = getgrgid(egid)) != NULL) + (void)printf("(%s)", gr->gr_name); + } + if ((ngroups = getgroups(maxgroups, groups)) != 0) { + for (fmt = " groups=%ju", lastid = -1, cnt = 0; cnt < ngroups; + fmt = ",%ju", lastid = gid, cnt++) { + gid = groups[cnt]; + if (lastid == gid) + continue; + (void)printf(fmt, (uintmax_t)gid); + if ((gr = getgrgid(gid)) != NULL) + (void)printf("(%s)", gr->gr_name); + } + } + (void)printf("\n"); +} + +static void +user(struct passwd *pw) +{ + struct group *gr; + const char *fmt; + int cnt, id, lastid, ngroups; + gid_t *glist = groups; + + id = pw->pw_uid; + (void)printf("uid=%u(%s)", id, pw->pw_name); + (void)printf(" gid=%lu", (u_long)pw->pw_gid); + if ((gr = getgrgid(pw->pw_gid)) != NULL) + (void)printf("(%s)", gr->gr_name); + ngroups = maxgroups + 1; + if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups) == -1) { + glist = malloc(ngroups * sizeof(gid_t)); + (void) getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups); + } + for (fmt = " groups=%u", lastid = -1, cnt = 0; cnt < ngroups; + fmt=",%u", lastid = id, cnt++) { + id = glist[cnt]; + if (lastid == id) + continue; + (void)printf(fmt, id); + if ((gr = getgrgid(id)) != NULL) + (void)printf("(%s)", gr->gr_name); + } + (void)printf("\n"); + if (glist != groups) + free(glist); +} + +static void +group(struct passwd *pw, int nflag) +{ + struct group *gr; + int cnt, ngroups; + gid_t id, lastid; + const char *fmt; + gid_t *glist = groups; + + if (pw) { + ngroups = maxgroups; + if (getgrouplist(pw->pw_name, pw->pw_gid, glist, &ngroups) + == -1) { + glist = malloc(ngroups * sizeof(gid_t)); + (void) getgrouplist(pw->pw_name, pw->pw_gid, glist, + &ngroups); + } + } else { + glist[0] = getgid(); + ngroups = getgroups(maxgroups, glist + 1) + 1; + } + fmt = nflag ? "%s" : "%u"; + for (lastid = -1, cnt = 0; cnt < ngroups; ++cnt) { + if (lastid == (id = glist[cnt]) || (cnt && id == glist[0])) + continue; + if (nflag) { + if ((gr = getgrgid(id)) != NULL) + (void)printf(fmt, gr->gr_name); + else + (void)printf(*fmt == ' ' ? " %u" : "%u", + id); + fmt = " %s"; + } else { + (void)printf(fmt, id); + fmt = " %u"; + } + lastid = id; + } + (void)printf("\n"); + if (glist != groups) + free(glist); +} + +static struct passwd * +who(char *u) +{ + struct passwd *pw; + long id; + char *ep; + + /* + * Translate user argument into a pw pointer. First, try to + * get it as specified. If that fails, try it as a number. + */ + if ((pw = getpwnam(u)) != NULL) + return pw; + id = strtol(u, &ep, 10); + if (*u && !*ep && (pw = getpwuid(id))) + return pw; + errx(1, "%s: No such user", u); + /* NOTREACHED */ + return NULL; +} + +static void +usage(void) +{ + + if (strcmp(getprogname(), "groups") == 0) { + (void)fprintf(stderr, "usage: groups [user]\n"); + } else if (strcmp(getprogname(), "whoami") == 0) { + (void)fprintf(stderr, "usage: whoami\n"); + } else { + (void)fprintf(stderr, "usage: id [user]\n"); + (void)fprintf(stderr, " id -G [-n] [user]\n"); + (void)fprintf(stderr, " id -g [-nr] [user]\n"); + (void)fprintf(stderr, " id -p [user]\n"); + (void)fprintf(stderr, " id -u [-nr] [user]\n"); + } + exit(1); +} diff --git a/usr.bin/id/whoami.1 b/usr.bin/id/whoami.1 new file mode 100644 index 0000000..53d9405 --- /dev/null +++ b/usr.bin/id/whoami.1 @@ -0,0 +1,58 @@ +.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)whoami.1 8.1 (Berkeley) 6/6/93 +.\" $NetBSD: whoami.1,v 1.10 2016/10/02 21:06:18 abhinav Exp $ +.\" +.Dd June 6, 1993 +.Dt WHOAMI 1 +.Os +.Sh NAME +.Nm whoami +.Nd display effective user id +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility has been obsoleted by the +.Xr id 1 +utility, and is equivalent to +.Dq Nm id Fl un . +The command +.Dq Nm id Fl p +is suggested for normal interactive use. +.Pp +The +.Nm +utility displays your effective user ID as a name. +.Sh EXIT STATUS +.Ex -std whoami +.Sh SEE ALSO +.Xr id 1 diff --git a/usr.bin/ipcrm/ipcrm.1 b/usr.bin/ipcrm/ipcrm.1 new file mode 100644 index 0000000..2e2cad3 --- /dev/null +++ b/usr.bin/ipcrm/ipcrm.1 @@ -0,0 +1,97 @@ +.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $ +.\" +.\" Copyright (c) 1994 Adam Glass +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. The name of the Author may not be used to endorse or promote products +.\" derived from this software without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $NetBSD: ipcrm.1,v 1.11 2008/06/01 10:25:29 wiz Exp $ +.\" +.Dd May 31, 2008 +.Dt IPCRM 1 +.Os +.Sh NAME +.Nm ipcrm +.Nd remove the specified message queues, semaphore sets, and shared memory segments +.Sh SYNOPSIS +.Nm +.Op Fl M Ar shmkey +.Op Fl m Ar shmid +.Op Fl Q Ar msgkey +.Op Fl q Ar msqid +.Op Fl S Ar semkey +.Op Fl s Ar semid +.Ar ... +.Sh DESCRIPTION +.Nm +removes the specified message queues, semaphores, and shared memory +segments. +These System V IPC objects can be specified by their +creation ID or any associated key. +.Pp +The following options are used to specify which IPC objects will be removed. +Any number and combination of these options can be used: +.Bl -tag -width indent +.It Fl M Ar shmkey +Mark the shared memory segment associated with key +.Ar shmkey +for removal. +This marked segment will be destroyed after the last detach. +.It Fl m Ar shmid +Mark the shared memory segment associated with ID +.Ar shmid +for removal. +This marked segment will be destroyed after the last detach. +.It Fl Q Ar msgkey +Remove the message queue associated with key +.Ar msgkey +from the system. +.It Fl q Ar msqid +Remove the message queue associated with the ID +.Ar msqid +from the system. +.It Fl S Ar semkey +Remove the semaphore set associated with key +.Ar semkey +from the system. +.It Fl s Ar semid +Removes the semaphore set associated with ID +.Ar semid +from the system. +.El +.Pp +If the +.Ar id +or +.Ar key +argument is +.Dq all +then all entries of the appropriate type are removed. +.Pp +The identifiers and keys associated with these System V IPC objects can be +determined by using +.Xr ipcs 1 . +.Sh SEE ALSO +.Xr ipcs 1 , +.Xr shmat 2 , +.Xr shmctl 2 , +.Xr shmdt 2 , +.Xr shmget 2 diff --git a/usr.bin/ipcrm/ipcrm.c b/usr.bin/ipcrm/ipcrm.c new file mode 100644 index 0000000..b4d3706 --- /dev/null +++ b/usr.bin/ipcrm/ipcrm.c @@ -0,0 +1,313 @@ +/* $NetBSD: ipcrm.c,v 1.16 2009/01/18 01:06:42 lukem Exp $ */ + +/* + * Copyright (c) 1994 Adam Glass + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Adam Glass. + * 4. The name of the Author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY Adam Glass ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL Adam Glass BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define IPC_TO_STR(x) (x == 'Q' ? "msq" : (x == 'M' ? "shm" : "sem")) +#define IPC_TO_STRING(x) (x == 'Q' ? "message queue" : \ + (x == 'M' ? "shared memory segment" : "semaphore")) + +static sig_atomic_t signaled; + +static void usage(void) __dead; +static int msgrm(key_t, int); +static int shmrm(key_t, int); +static int semrm(key_t, int); +static int msgrmall(void); +static int shmrmall(void); +static int semrmall(void); +static void not_configured(int); + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-M shmkey] [-m shmid] [-Q msgkey]\n", + getprogname()); + (void)fprintf(stderr, "\t[-q msqid] [-S semkey] [-s semid] ...\n"); + exit(1); +} + +static int +msgrm(key_t key, int id) +{ + if (key) { + id = msgget(key, 0); + if (id == -1) + return -1; + } + return msgctl(id, IPC_RMID, NULL); +} + +static int +shmrm(key_t key, int id) +{ + if (key) { + id = shmget(key, 0, 0); + if (id == -1) + return -1; + } + return shmctl(id, IPC_RMID, NULL); +} + +static int +semrm(key_t key, int id) +{ + + if (key) { + id = semget(key, 0, 0); + if (id == -1) + return -1; + } + return semctl(id, 0, IPC_RMID, NULL); +} + +static int +msgrmall(void) +{ + int mib[4]; + struct msg_sysctl_info *msgsi; + int32_t i; + size_t len; + int result = 0; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_MSG_INFO; + + if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) + err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)"); + + if ((msgsi = malloc(len)) == NULL) + err(1, "malloc"); + if (sysctl(mib, 4, msgsi, &len, NULL, 0) == -1) { + free(msgsi); + err(1, "sysctl(KERN_SYSVIPC_MSG_INFO)"); + } + + for (i = 0; i < msgsi->msginfo.msgmni; i++) { + struct msgid_ds_sysctl *msgptr = &msgsi->msgids[i]; + if (msgptr->msg_qbytes != 0) + result -= msgrm((key_t)0, + (int)IXSEQ_TO_IPCID(i, msgptr->msg_perm)); + } + free(msgsi); + return result; +} + +static int +shmrmall(void) +{ + int mib[4]; + struct shm_sysctl_info *shmsi; + size_t i, len; + int result = 0; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_SHM_INFO; + + if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) + err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)"); + + if ((shmsi = malloc(len)) == NULL) + err(1, "malloc"); + if (sysctl(mib, 4, shmsi, &len, NULL, 0) == -1) { + free(shmsi); + err(1, "sysctl(KERN_SYSVIPC_SHM_INFO)"); + } + + for (i = 0; i < shmsi->shminfo.shmmni; i++) { + struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i]; + if (shmptr->shm_perm.mode & 0x0800) + result -= shmrm((key_t)0, + (int)IXSEQ_TO_IPCID(i, shmptr->shm_perm)); + } + free(shmsi); + return result; +} + +static int +semrmall(void) +{ + int mib[4]; + struct sem_sysctl_info *semsi; + size_t len; + int32_t i; + int result = 0; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_SEM_INFO; + + if (sysctl(mib, 4, NULL, &len, NULL, 0) == -1) + err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)"); + + if ((semsi = malloc(len)) == NULL) + err(1, "malloc"); + if (sysctl(mib, 4, semsi, &len, NULL, 0) == -1) { + free(semsi); + err(1, "sysctl(KERN_SYSVIPC_SEM_INFO)"); + } + + for (i = 0; i < semsi->seminfo.semmni; i++) { + struct semid_ds_sysctl *semptr = &semsi->semids[i]; + if ((semptr->sem_perm.mode & SEM_ALLOC) != 0) + result -= semrm((key_t)0, + (int)IXSEQ_TO_IPCID(i, semptr->sem_perm)); + } + free(semsi); + return result; +} + +static void +/*ARGSUSED*/ +not_configured(int n) +{ + signaled++; +} + +int +main(int argc, char *argv[]) +{ + int c, result, errflg, target_id; + key_t target_key; + + setprogname(argv[0]); + errflg = 0; + (void)signal(SIGSYS, not_configured); + while ((c = getopt(argc, argv, "q:m:s:Q:M:S:")) != -1) { + signaled = 0; + target_id = 0; + target_key = 0; + result = 0; + + if (optarg != NULL && strcmp(optarg, "all") == 0) { + switch (c) { + case 'm': + case 'M': + result = shmrmall(); + break; + case 'q': + case 'Q': + result = msgrmall(); + break; + case 's': + case 'S': + result = semrmall(); + break; + default: + usage(); + } + } else { + switch (c) { + case 'q': + case 'm': + case 's': + target_id = atoi(optarg); + break; + case 'Q': + case 'M': + case 'S': + target_key = atol(optarg); + if (target_key == IPC_PRIVATE) { + warnx("can't remove private %ss", + IPC_TO_STRING(c)); + continue; + } + break; + default: + usage(); + } + switch (c) { + case 'q': + result = msgrm((key_t)0, target_id); + break; + case 'm': + result = shmrm((key_t)0, target_id); + break; + case 's': + result = semrm((key_t)0, target_id); + break; + case 'Q': + result = msgrm(target_key, 0); + break; + case 'M': + result = shmrm(target_key, 0); + break; + case 'S': + result = semrm(target_key, 0); + break; + } + } + if (result < 0) { + if (!signaled) { + if (target_id) { + warn("%sid(%d): ", + IPC_TO_STR(toupper(c)), target_id); + errflg++; + } else if (target_key) { + warn("%skey(%ld): ", IPC_TO_STR(c), + (long)target_key); + errflg++; + } + } else { + errflg++; + warnx("%ss are not configured in " + "the running kernel", + IPC_TO_STRING(toupper(c))); + } + } + } + + if (optind != argc) { + warnx("Unknown argument: %s", argv[optind]); + usage(); + } + return errflg; +} diff --git a/usr.bin/ipcs/ipcs.1 b/usr.bin/ipcs/ipcs.1 new file mode 100644 index 0000000..86bd34e --- /dev/null +++ b/usr.bin/ipcs/ipcs.1 @@ -0,0 +1,141 @@ +.\" $NetBSD: ipcs.1,v 1.20 2014/06/11 14:57:55 joerg Exp $ +.\" +.\" Copyright (c) 1994 SigmaSoft, Th. Lockert +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +.\" IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +.\" OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +.\" IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +.\" NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +.\" DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +.\" THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +.\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +.\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 11, 2014 +.Dt IPCS 1 +.Os +.Sh NAME +.Nm ipcs +.Nd report System V interprocess communication facilities status +.Sh SYNOPSIS +.Nm +.Op Fl abcmopqstMQST +.Sh DESCRIPTION +The +.Nm +program provides information on System V interprocess communication +(IPC) facilities on the system. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Show the maximum amount of information possible when +displaying active semaphores, message queues, +and shared memory segments. +(This is shorthand for specifying the +.Fl b , +.Fl c , +.Fl o , +.Fl p , +and +.Fl t +options.) +.It Fl b +Show the maximum allowed sizes for active semaphores, message queues, +and shared memory segments. +The +.Dq maximum allowed size +is the maximum number of bytes in a message on a message queue, +the size of a shared memory segment, +or the number of semaphores in a set of semaphores. +.It Fl c +Show the creator's name and group for active semaphores, message queues, +and shared memory segments. +.It Fl m +Display information about active shared memory segments. +.It Fl o +Show outstanding usage for active message queues, +and shared memory segments. +The +.Dq outstanding usage +is the number of messages in a message queue, or the number +of processes attached to a shared memory segment. +.It Fl p +Show the process ID information for active semaphores, message queues, +and shared memory segments. +The +.Dq process ID information +is the last process to send a message to or receive a message from +a message queue, +the process that created a semaphore, or the last process to attach +or detach a shared memory segment. +.It Fl q +Display information about active message queues. +.It Fl s +Display information about active semaphores. +.It Fl t +Show access times for active semaphores, message queues, +and shared memory segments. +The access times is the time +of the last control operation on an IPC object, +the last send or receive of a message, +the last attach or detach of a shared memory segment, +or the last operation on a semaphore. +.It Fl M +Display system information about shared memory. +.It Fl Q +Display system information about messages queues. +.It Fl S +Display system information about semaphores. +.It Fl T +Display system information about shared memory, message queues +and semaphores. +(This is shorthand for specifying the +.Fl M , +.Fl Q , +and +.Fl S +options.) +.El +.Pp +If none of the +.Fl M , +.Fl m , +.Fl Q , +.Fl q , +.Fl S , +.Fl s , +or +.Fl T +options are specified, information about all active IPC facilities is +listed. +.Sh RESTRICTIONS +System data structures may change while +.Nm +is running; the output of +.Nm +is not guaranteed to be consistent. +.Sh SEE ALSO +.Xr ipcrm 1 , +.Xr shmat 2 , +.Xr shmctl 2 , +.Xr shmdt 2 , +.Xr shmget 2 +.Sh AUTHORS +.An Thorsten Lockert Aq Mt tholo@sigmasoft.com +.Sh BUGS +This manual page is woefully incomplete, because it does not +at all attempt to explain the information printed by +.Nm . diff --git a/usr.bin/ipcs/ipcs.c b/usr.bin/ipcs/ipcs.c new file mode 100644 index 0000000..8fbae90 --- /dev/null +++ b/usr.bin/ipcs/ipcs.c @@ -0,0 +1,678 @@ +/* $NetBSD: ipcs.c,v 1.43 2014/06/11 14:57:55 joerg Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Simon Burge. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright (c) 1994 SigmaSoft, Th. Lockert + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SHMINFO 1 +#define SHMTOTAL 2 +#define MSGINFO 4 +#define MSGTOTAL 8 +#define SEMINFO 16 +#define SEMTOTAL 32 + +#define BIGGEST 1 +#define CREATOR 2 +#define OUTSTANDING 4 +#define PID 8 +#define TIME 16 + +static int display = 0; +static int option = 0; + +static void cvt_time(time_t, char *, size_t); +static char *fmt_perm(u_short); +static void msg_sysctl(void); +static void sem_sysctl(void); +static void shm_sysctl(void); +static void show_msginfo(time_t, time_t, time_t, int, u_int64_t, mode_t, + uid_t, gid_t, uid_t, gid_t, u_int64_t, u_int64_t, u_int64_t, pid_t, pid_t); +static void show_msginfo_hdr(void); +static void show_msgtotal(struct msginfo *); +static void show_seminfo_hdr(void); +static void show_seminfo(time_t, time_t, int, u_int64_t, mode_t, uid_t, + gid_t, uid_t, gid_t, int16_t); +static void show_semtotal(struct seminfo *); +static void show_shminfo(time_t, time_t, time_t, int, u_int64_t, mode_t, + uid_t, gid_t, uid_t, gid_t, u_int32_t, u_int64_t, pid_t, pid_t); +static void show_shminfo_hdr(void); +static void show_shmtotal(struct shminfo *); +static void usage(void) __dead; +static void unconfsem(void); +static void unconfmsg(void); +static void unconfshm(void); + +static void +unconfsem(void) +{ + warnx("SVID semaphores facility not configured in the system"); +} + +static void +unconfmsg(void) +{ + warnx("SVID messages facility not configured in the system"); +} + +static void +unconfshm(void) +{ + warnx("SVID shared memory facility not configured in the system"); +} + +static char * +fmt_perm(u_short mode) +{ + static char buffer[12]; + + buffer[0] = '-'; + buffer[1] = '-'; + buffer[2] = ((mode & 0400) ? 'r' : '-'); + buffer[3] = ((mode & 0200) ? 'w' : '-'); + buffer[4] = ((mode & 0100) ? 'a' : '-'); + buffer[5] = ((mode & 0040) ? 'r' : '-'); + buffer[6] = ((mode & 0020) ? 'w' : '-'); + buffer[7] = ((mode & 0010) ? 'a' : '-'); + buffer[8] = ((mode & 0004) ? 'r' : '-'); + buffer[9] = ((mode & 0002) ? 'w' : '-'); + buffer[10] = ((mode & 0001) ? 'a' : '-'); + buffer[11] = '\0'; + return (&buffer[0]); +} + +static void +cvt_time(time_t t, char *buf, size_t buflen) +{ + struct tm *tm; + + if (t == 0) + (void)strlcpy(buf, "no-entry", buflen); + else { + tm = localtime(&t); + (void)snprintf(buf, buflen, "%2d:%02d:%02d", + tm->tm_hour, tm->tm_min, tm->tm_sec); + } +} +int +main(int argc, char *argv[]) +{ + int i; + time_t now; + + while ((i = getopt(argc, argv, "MmQqSsabcoptT")) != -1) + switch (i) { + case 'M': + display |= SHMTOTAL; + break; + case 'm': + display |= SHMINFO; + break; + case 'Q': + display |= MSGTOTAL; + break; + case 'q': + display |= MSGINFO; + break; + case 'S': + display |= SEMTOTAL; + break; + case 's': + display |= SEMINFO; + break; + case 'T': + display |= SHMTOTAL | MSGTOTAL | SEMTOTAL; + break; + case 'a': + option |= BIGGEST | CREATOR | OUTSTANDING | PID | TIME; + break; + case 'b': + option |= BIGGEST; + break; + case 'c': + option |= CREATOR; + break; + case 'o': + option |= OUTSTANDING; + break; + case 'p': + option |= PID; + break; + case 't': + option |= TIME; + break; + default: + usage(); + } + + if (argc - optind > 0) + usage(); + + (void)time(&now); + (void)printf("IPC status from as of %s\n", + /* and extra \n from ctime(3) */ + ctime(&now)); + + if (display == 0) + display = SHMINFO | MSGINFO | SEMINFO; + + if (display & (MSGINFO | MSGTOTAL)) + msg_sysctl(); + if (display & (SHMINFO | SHMTOTAL)) + shm_sysctl(); + if (display & (SEMINFO | SEMTOTAL)) + sem_sysctl(); + return 0; +} + +static void +show_msgtotal(struct msginfo *msginfo) +{ + (void)printf("msginfo:\n"); + (void)printf("\tmsgmax: %6d\t(max characters in a message)\n", + msginfo->msgmax); + (void)printf("\tmsgmni: %6d\t(# of message queues)\n", + msginfo->msgmni); + (void)printf("\tmsgmnb: %6d\t(max characters in a message queue)\n", + msginfo->msgmnb); + (void)printf("\tmsgtql: %6d\t(max # of messages in system)\n", + msginfo->msgtql); + (void)printf("\tmsgssz: %6d\t(size of a message segment)\n", + msginfo->msgssz); + (void)printf("\tmsgseg: %6d\t(# of message segments in system)\n\n", + msginfo->msgseg); +} + +static void +show_shmtotal(struct shminfo *shminfo) +{ + (void)printf("shminfo:\n"); + (void)printf("\tshmmax: %" PRIu64 "\t(max shared memory segment size)\n", + shminfo->shmmax); + (void)printf("\tshmmin: %7d\t(min shared memory segment size)\n", + shminfo->shmmin); + (void)printf("\tshmmni: %7d\t(max number of shared memory identifiers)\n", + shminfo->shmmni); + (void)printf("\tshmseg: %7d\t(max shared memory segments per process)\n", + shminfo->shmseg); + (void)printf("\tshmall: %7d\t(max amount of shared memory in pages)\n\n", + shminfo->shmall); +} + +static void +show_semtotal(struct seminfo *seminfo) +{ + (void)printf("seminfo:\n"); + (void)printf("\tsemmap: %6d\t(# of entries in semaphore map)\n", + seminfo->semmap); + (void)printf("\tsemmni: %6d\t(# of semaphore identifiers)\n", + seminfo->semmni); + (void)printf("\tsemmns: %6d\t(# of semaphores in system)\n", + seminfo->semmns); + (void)printf("\tsemmnu: %6d\t(# of undo structures in system)\n", + seminfo->semmnu); + (void)printf("\tsemmsl: %6d\t(max # of semaphores per id)\n", + seminfo->semmsl); + (void)printf("\tsemopm: %6d\t(max # of operations per semop call)\n", + seminfo->semopm); + (void)printf("\tsemume: %6d\t(max # of undo entries per process)\n", + seminfo->semume); + (void)printf("\tsemusz: %6d\t(size in bytes of undo structure)\n", + seminfo->semusz); + (void)printf("\tsemvmx: %6d\t(semaphore maximum value)\n", + seminfo->semvmx); + (void)printf("\tsemaem: %6d\t(adjust on exit max value)\n\n", + seminfo->semaem); +} + +static void +show_msginfo_hdr(void) +{ + (void)printf("Message Queues:\n"); + (void)printf("T ID KEY MODE OWNER GROUP"); + if (option & CREATOR) + (void)printf(" CREATOR CGROUP"); + if (option & OUTSTANDING) + (void)printf(" CBYTES QNUM"); + if (option & BIGGEST) + (void)printf(" QBYTES"); + if (option & PID) + (void)printf(" LSPID LRPID"); + if (option & TIME) + (void)printf(" STIME RTIME CTIME"); + (void)printf("\n"); +} + +static void +show_msginfo(time_t s_time, time_t r_time, time_t c_time, int ipcid, + u_int64_t key, + mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid, + u_int64_t cbytes, u_int64_t qnum, u_int64_t qbytes, pid_t lspid, + pid_t lrpid) +{ + char s_time_buf[100], r_time_buf[100], c_time_buf[100]; + + if (option & TIME) { + cvt_time(s_time, s_time_buf, sizeof(s_time_buf)); + cvt_time(r_time, r_time_buf, sizeof(r_time_buf)); + cvt_time(c_time, c_time_buf, sizeof(c_time_buf)); + } + + (void)printf("q %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode), + user_from_uid(uid, 0), group_from_gid(gid, 0)); + + if (option & CREATOR) + (void)printf(" %8s %8s", user_from_uid(cuid, 0), + group_from_gid(cgid, 0)); + + if (option & OUTSTANDING) + (void)printf(" %6lld %5lld", (long long)cbytes, (long long)qnum); + + if (option & BIGGEST) + (void)printf(" %6lld", (long long)qbytes); + + if (option & PID) + (void)printf(" %5d %5d", lspid, lrpid); + + if (option & TIME) + (void)printf(" %s %s %s", s_time_buf, r_time_buf, c_time_buf); + + (void)printf("\n"); +} + +static void +show_shminfo_hdr(void) +{ + (void)printf("Shared Memory:\n"); + (void)printf("T ID KEY MODE OWNER GROUP"); + if (option & CREATOR) + (void)printf(" CREATOR CGROUP"); + if (option & OUTSTANDING) + (void)printf(" NATTCH"); + if (option & BIGGEST) + (void)printf(" SEGSZ"); + if (option & PID) + (void)printf(" CPID LPID"); + if (option & TIME) + (void)printf(" ATIME DTIME CTIME"); + (void)printf("\n"); +} + +static void +show_shminfo(time_t atime, time_t dtime, time_t c_time, int ipcid, u_int64_t key, + mode_t mode, uid_t uid, gid_t gid, uid_t cuid, gid_t cgid, + u_int32_t nattch, u_int64_t segsz, pid_t cpid, pid_t lpid) +{ + char atime_buf[100], dtime_buf[100], c_time_buf[100]; + + if (option & TIME) { + cvt_time(atime, atime_buf, sizeof(atime_buf)); + cvt_time(dtime, dtime_buf, sizeof(dtime_buf)); + cvt_time(c_time, c_time_buf, sizeof(c_time_buf)); + } + + (void)printf("m %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode), + user_from_uid(uid, 0), group_from_gid(gid, 0)); + + if (option & CREATOR) + (void)printf(" %8s %8s", user_from_uid(cuid, 0), + group_from_gid(cgid, 0)); + + if (option & OUTSTANDING) + (void)printf(" %6d", nattch); + + if (option & BIGGEST) + (void)printf(" %7llu", (long long)segsz); + + if (option & PID) + (void)printf(" %5d %5d", cpid, lpid); + + if (option & TIME) + (void)printf(" %s %s %s", + atime_buf, + dtime_buf, + c_time_buf); + + (void)printf("\n"); +} + +static void +show_seminfo_hdr(void) +{ + (void)printf("Semaphores:\n"); + (void)printf("T ID KEY MODE OWNER GROUP"); + if (option & CREATOR) + (void)printf(" CREATOR CGROUP"); + if (option & BIGGEST) + (void)printf(" NSEMS"); + if (option & TIME) + (void)printf(" OTIME CTIME"); + (void)printf("\n"); +} + +static void +show_seminfo(time_t otime, time_t c_time, int ipcid, u_int64_t key, mode_t mode, + uid_t uid, gid_t gid, uid_t cuid, gid_t cgid, int16_t nsems) +{ + char c_time_buf[100], otime_buf[100]; + + if (option & TIME) { + cvt_time(otime, otime_buf, sizeof(otime_buf)); + cvt_time(c_time, c_time_buf, sizeof(c_time_buf)); + } + + (void)printf("s %9d %10lld %s %8s %8s", ipcid, (long long)key, fmt_perm(mode), + user_from_uid(uid, 0), group_from_gid(gid, 0)); + + if (option & CREATOR) + (void)printf(" %8s %8s", user_from_uid(cuid, 0), + group_from_gid(cgid, 0)); + + if (option & BIGGEST) + (void)printf(" %5d", nsems); + + if (option & TIME) + (void)printf(" %s %s", otime_buf, c_time_buf); + + (void)printf("\n"); +} + +static void +msg_sysctl(void) +{ + struct msg_sysctl_info *msgsi; + void *buf; + int mib[4]; + size_t len; + int i, valid; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_MSG; + len = sizeof(valid); + if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_MSG)"); + return; + } + if (!valid) { + unconfmsg(); + return; + } + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_MSG_INFO; + + if (!(display & MSGINFO)) { + /* totals only */ + len = sizeof(struct msginfo); + } else { + if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_MSG_INFO)"); + return; + } + } + + if ((buf = malloc(len)) == NULL) + err(1, "malloc"); + msgsi = (struct msg_sysctl_info *)buf; + if (sysctl(mib, 4, msgsi, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_MSG_INFO)"); + goto done; + } + + if (display & MSGTOTAL) + show_msgtotal(&msgsi->msginfo); + + if (display & MSGINFO) { + show_msginfo_hdr(); + for (i = 0; i < msgsi->msginfo.msgmni; i++) { + struct msgid_ds_sysctl *msqptr = &msgsi->msgids[i]; + if (msqptr->msg_qbytes != 0) + show_msginfo(msqptr->msg_stime, + msqptr->msg_rtime, + msqptr->msg_ctime, + IXSEQ_TO_IPCID(i, msqptr->msg_perm), + msqptr->msg_perm._key, + msqptr->msg_perm.mode, + msqptr->msg_perm.uid, + msqptr->msg_perm.gid, + msqptr->msg_perm.cuid, + msqptr->msg_perm.cgid, + msqptr->_msg_cbytes, + msqptr->msg_qnum, + msqptr->msg_qbytes, + msqptr->msg_lspid, + msqptr->msg_lrpid); + } + (void)printf("\n"); + } +done: + free(buf); +} + +static void +shm_sysctl(void) +{ + struct shm_sysctl_info *shmsi; + void *buf; + int mib[4]; + size_t len; + uint32_t i; + long valid; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_SHM; + len = sizeof(valid); + if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SHM)"); + return; + } + if (!valid) { + unconfshm(); + return; + } + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_SHM_INFO; + + if (!(display & SHMINFO)) { + /* totals only */ + len = sizeof(struct shminfo); + } else { + if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SHM_INFO)"); + return; + } + } + + if ((buf = malloc(len)) == NULL) + err(1, "malloc"); + shmsi = (struct shm_sysctl_info *)buf; + if (sysctl(mib, 4, shmsi, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SHM_INFO)"); + goto done; + } + + if (display & SHMTOTAL) + show_shmtotal(&shmsi->shminfo); + + if (display & SHMINFO) { + show_shminfo_hdr(); + for (i = 0; i < shmsi->shminfo.shmmni; i++) { + struct shmid_ds_sysctl *shmptr = &shmsi->shmids[i]; + if (shmptr->shm_perm.mode & 0x0800) + show_shminfo(shmptr->shm_atime, + shmptr->shm_dtime, + shmptr->shm_ctime, + IXSEQ_TO_IPCID(i, shmptr->shm_perm), + shmptr->shm_perm._key, + shmptr->shm_perm.mode, + shmptr->shm_perm.uid, + shmptr->shm_perm.gid, + shmptr->shm_perm.cuid, + shmptr->shm_perm.cgid, + shmptr->shm_nattch, + shmptr->shm_segsz, + shmptr->shm_cpid, + shmptr->shm_lpid); + } + (void)printf("\n"); + } +done: + free(buf); +} + +static void +sem_sysctl(void) +{ + struct sem_sysctl_info *semsi; + void *buf; + int mib[4]; + size_t len; + int i, valid; + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_SEM; + len = sizeof(valid); + if (sysctl(mib, 3, &valid, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SEM)"); + return; + } + if (!valid) { + unconfsem(); + return; + } + + mib[0] = CTL_KERN; + mib[1] = KERN_SYSVIPC; + mib[2] = KERN_SYSVIPC_INFO; + mib[3] = KERN_SYSVIPC_SEM_INFO; + + if (!(display & SEMINFO)) { + /* totals only */ + len = sizeof(struct seminfo); + } else { + if (sysctl(mib, 4, NULL, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SEM_INFO)"); + return; + } + } + + if ((buf = malloc(len)) == NULL) + err(1, "malloc"); + semsi = (struct sem_sysctl_info *)buf; + if (sysctl(mib, 4, semsi, &len, NULL, 0) < 0) { + warn("sysctl(KERN_SYSVIPC_SEM_INFO)"); + goto done; + } + + if (display & SEMTOTAL) + show_semtotal(&semsi->seminfo); + + if (display & SEMINFO) { + show_seminfo_hdr(); + for (i = 0; i < semsi->seminfo.semmni; i++) { + struct semid_ds_sysctl *semaptr = &semsi->semids[i]; + if ((semaptr->sem_perm.mode & SEM_ALLOC) != 0) + show_seminfo(semaptr->sem_otime, + semaptr->sem_ctime, + IXSEQ_TO_IPCID(i, semaptr->sem_perm), + semaptr->sem_perm._key, + semaptr->sem_perm.mode, + semaptr->sem_perm.uid, + semaptr->sem_perm.gid, + semaptr->sem_perm.cuid, + semaptr->sem_perm.cgid, + semaptr->sem_nsems); + } + (void)printf("\n"); + } +done: + free(buf); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-abcmopqstMQST]\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/join/join.1 b/usr.bin/join/join.1 new file mode 100644 index 0000000..d40b099 --- /dev/null +++ b/usr.bin/join/join.1 @@ -0,0 +1,206 @@ +.\" $NetBSD: join.1,v 1.15 2017/07/04 07:01:53 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)join.1 8.3 (Berkeley) 4/28/95 +.\" +.Dd April 28, 1995 +.Dt JOIN 1 +.Os +.Sh NAME +.Nm join +.Nd relational database operator +.Sh SYNOPSIS +.Nm +.Op Fl a Ar file_number | Fl v Ar file_number +.Op Fl e Ar string +.Op Fl j Ar file_number field +.Op Fl o Ar list +.Op Fl t Ar char +.Op Fl \&1 Ar field +.Op Fl \&2 Ar field +.Ar file1 file2 +.Sh DESCRIPTION +The join utility performs an ``equality join'' on the specified files +and writes the result to the standard output. +The ``join field'' is the field in each file by which the files are compared. +The first field in each line is used by default. +There is one line in the output for each pair of lines in +.Ar file1 +and +.Ar file2 +which have identical join fields. +Each output line consists of the join field, the remaining fields from +.Ar file1 +and then the remaining fields from +.Ar file2 . +.Pp +The default field separators are tab and space characters. +In this case, multiple tabs and spaces count as a single field separator, +and leading tabs and spaces are ignored. +The default output field separator is a single space character. +.Pp +Many of the options use file and field numbers. +Both file numbers and field numbers are 1 based, i.e. the first file on +the command line is file number 1 and the first field is field number 1. +The following options are available: +.Bl -tag -width Fl +.It Fl a Ar file_number +In addition to the default output, produce a line for each unpairable +line in file +.Ar file_number . +(The argument to +.Fl a +must not be preceded by a space; see the +.Sx COMPATIBILITY +section.) +.It Fl e Ar string +Replace empty output fields with +.Ar string . +.It Fl o Ar list +The +.Fl o +option specifies the fields that will be output from each file for +each line with matching join fields. +Each element of +.Ar list +has the form +.Ql file_number.field , +where +.Ar file_number +is a file number and +.Ar field +is a field number. +The elements of list must be either comma (``,'') or whitespace separated. +(The latter requires quoting to protect it from the shell, or, a simpler +approach is to use multiple +.Fl o +options.) +.It Fl t Ar char +Use character +.Ar char +as a field delimiter for both input and output. +Every occurrence of +.Ar char +in a line is significant. +.It Fl v Ar file_number +Do not display the default output, but display a line for each unpairable +line in file +.Ar file_number . +The options +.Fl v Ar 1 +and +.Fl v Ar 2 +may be specified at the same time. +.It Fl 1 Ar field +Join on the +.Ar field Ns 'th +field of file 1. +.It Fl 2 Ar field +Join on the +.Ar field Ns 'th +field of file 2. +.El +.Pp +When the default field delimiter characters are used, the files to be joined +should be ordered in the collating sequence of +.Xr sort 1 , +using the +.Fl b +option, on the fields on which they are to be joined, otherwise +.Nm +may not report all field matches. +When the field delimiter characters are specified by the +.Fl t +option, the collating sequence should be the same as +.Xr sort 1 +without the +.Fl b +option. +.Pp +If one of the arguments +.Ar file1 +or +.Ar file2 +is ``-'', the standard input is used. +.Sh EXIT STATUS +.Ex -std join +.Sh COMPATIBILITY +For compatibility with historic versions of +.Nm , +the following options are available: +.Bl -tag -width Fl +.It Fl a +In addition to the default output, produce a line for each unpairable line +in both file 1 and file 2. +(To distinguish between this and +.Fl a Ar file_number , +.Nm +currently requires that the latter not include any white space.) +.It Fl j1 Ar field +Join on the +.Ar field Ns 'th +field of file 1. +.It Fl j2 Ar field +Join on the +.Ar field Ns 'th +field of file 2. +.It Fl j Ar field +Join on the +.Ar field Ns 'th +field of both file 1 and file 2. +.It Fl o Ar list ... +Historical implementations of +.Nm +permitted multiple arguments to the +.Fl o +option. +These arguments were of the form ``file_number.field_number'' as described +for the current +.Fl o +option. +This has obvious difficulties in the presence of files named ``1.2''. +.El +.Pp +These options are available only so historic shell scripts don't require +modification and should not be used. +.Sh SEE ALSO +.Xr awk 1 , +.Xr comm 1 , +.Xr paste 1 , +.Xr sort 1 , +.Xr uniq 1 +.Sh STANDARDS +The +.Nm +command is expected to be +.St -p1003.2 +compatible. diff --git a/usr.bin/join/join.c b/usr.bin/join/join.c new file mode 100644 index 0000000..b377ec2 --- /dev/null +++ b/usr.bin/join/join.c @@ -0,0 +1,637 @@ +/* $NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $ */ + +/*- + * Copyright (c) 1991 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Steve Hayman of Indiana University, Michiro Hikida and David + * Goodenough. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "from: @(#)join.c 5.1 (Berkeley) 11/18/91"; +#else +__RCSID("$NetBSD: join.c,v 1.31 2011/09/04 20:27:52 joerg Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * There's a structure per input file which encapsulates the state of the + * file. We repeatedly read lines from each file until we've read in all + * the consecutive lines from the file with a common join field. Then we + * compare the set of lines with an equivalent set from the other file. + */ +typedef struct { + char *line; /* line */ + u_long linealloc; /* line allocated count */ + char **fields; /* line field(s) */ + u_long fieldcnt; /* line field(s) count */ + u_long fieldalloc; /* line field(s) allocated count */ +} LINE; + +static char nolineline[1] = { '\0' }; +static LINE noline = {nolineline, 0, 0, 0, 0}; /* arg for outfield if no line to output */ + +typedef struct { + FILE *fp; /* file descriptor */ + u_long joinf; /* join field (-1, -2, -j) */ + int unpair; /* output unpairable lines (-a) */ + int number; /* 1 for file 1, 2 for file 2 */ + + LINE *set; /* set of lines with same field */ + u_long pushback; /* line on the stack */ + u_long setcnt; /* set count */ + u_long setalloc; /* set allocated count */ +} INPUT; + +static INPUT input1 = { NULL, 0, 0, 1, NULL, -1, 0, 0, }, + input2 = { NULL, 0, 0, 2, NULL, -1, 0, 0, }; + +typedef struct { + u_long fileno; /* file number */ + u_long fieldno; /* field number */ +} OLIST; + +static OLIST *olist; /* output field list */ +static u_long olistcnt; /* output field list count */ +static u_long olistalloc; /* output field allocated count */ + +static int joinout = 1; /* show lines with matched join fields (-v) */ +static int needsep; /* need separator character */ +static int spans = 1; /* span multiple delimiters (-t) */ +static char *empty; /* empty field replacement string (-e) */ +static const char *tabchar = " \t"; /* delimiter characters (-t) */ + +static int cmp(LINE *, u_long, LINE *, u_long); +__dead static void enomem(void); +static void fieldarg(char *); +static void joinlines(INPUT *, INPUT *); +static void obsolete(char **); +static void outfield(LINE *, u_long); +static void outoneline(INPUT *, LINE *); +static void outtwoline(INPUT *, LINE *, INPUT *, LINE *); +static void slurp(INPUT *); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + INPUT *F1, *F2; + int aflag, ch, cval, vflag; + char *end; + + F1 = &input1; + F2 = &input2; + + aflag = vflag = 0; + obsolete(argv); + while ((ch = getopt(argc, argv, "\01a:e:j:1:2:o:t:v:")) != -1) { + switch (ch) { + case '\01': + aflag = 1; + F1->unpair = F2->unpair = 1; + break; + case '1': + if ((F1->joinf = strtol(optarg, &end, 10)) < 1) { + warnx("-1 option field number less than 1"); + usage(); + } + if (*end) { + warnx("illegal field number -- %s", optarg); + usage(); + } + --F1->joinf; + break; + case '2': + if ((F2->joinf = strtol(optarg, &end, 10)) < 1) { + warnx("-2 option field number less than 1"); + usage(); + } + if (*end) { + warnx("illegal field number -- %s", optarg); + usage(); + } + --F2->joinf; + break; + case 'a': + aflag = 1; + switch(strtol(optarg, &end, 10)) { + case 1: + F1->unpair = 1; + break; + case 2: + F2->unpair = 1; + break; + default: + warnx("-a option file number not 1 or 2"); + usage(); + break; + } + if (*end) { + warnx("illegal file number -- %s", optarg); + usage(); + } + break; + case 'e': + empty = optarg; + break; + case 'j': + if ((F1->joinf = F2->joinf = + strtol(optarg, &end, 10)) < 1) { + warnx("-j option field number less than 1"); + usage(); + } + if (*end) { + warnx("illegal field number -- %s", optarg); + usage(); + } + --F1->joinf; + --F2->joinf; + break; + case 'o': + fieldarg(optarg); + break; + case 't': + spans = 0; + if (strlen(tabchar = optarg) != 1) { + warnx("illegal tab character specification"); + usage(); + } + break; + case 'v': + vflag = 1; + joinout = 0; + switch(strtol(optarg, &end, 10)) { + case 1: + F1->unpair = 1; + break; + case 2: + F2->unpair = 1; + break; + default: + warnx("-v option file number not 1 or 2"); + usage(); + break; + } + if (*end) { + warnx("illegal file number -- %s", optarg); + usage(); + } + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (aflag && vflag) + errx(1, "-a and -v options mutually exclusive"); + + if (argc != 2) + usage(); + + /* Open the files; "-" means stdin. */ + if (!strcmp(*argv, "-")) + F1->fp = stdin; + else if ((F1->fp = fopen(*argv, "r")) == NULL) + err(1, "%s", *argv); + ++argv; + if (!strcmp(*argv, "-")) + F2->fp = stdin; + else if ((F2->fp = fopen(*argv, "r")) == NULL) + err(1, "%s", *argv); + if (F1->fp == stdin && F2->fp == stdin) + errx(1, "only one input file may be stdin"); + + slurp(F1); + slurp(F2); + while (F1->setcnt && F2->setcnt) { + cval = cmp(F1->set, F1->joinf, F2->set, F2->joinf); + if (cval == 0) { + /* Oh joy, oh rapture, oh beauty divine! */ + if (joinout) + joinlines(F1, F2); + slurp(F1); + slurp(F2); + } else if (cval < 0) { + /* File 1 takes the lead... */ + if (F1->unpair) + joinlines(F1, NULL); + slurp(F1); + } else { + /* File 2 takes the lead... */ + if (F2->unpair) + joinlines(F2, NULL); + slurp(F2); + } + } + + /* + * Now that one of the files is used up, optionally output any + * remaining lines from the other file. + */ + if (F1->unpair) + while (F1->setcnt) { + joinlines(F1, NULL); + slurp(F1); + } + if (F1->fp != stdin) + fclose(F1->fp); + + if (F2->unpair) + while (F2->setcnt) { + joinlines(F2, NULL); + slurp(F2); + } + if (F2->fp != stdin) + fclose(F2->fp); + + return 0; +} + +static void +slurp(INPUT *F) +{ + LINE *lp; + LINE tmp; + LINE *nline; + size_t len; + u_long cnt; + char *bp, *fieldp; + u_long nsize; + + /* + * Read all of the lines from an input file that have the same + * join field. + */ + for (F->setcnt = 0;; ++F->setcnt) { + /* + * If we're out of space to hold line structures, allocate + * more. Initialize the structure so that we know that this + * is new space. + */ + if (F->setcnt == F->setalloc) { + cnt = F->setalloc; + if (F->setalloc == 0) + nsize = 64; + else + nsize = F->setalloc << 1; + if ((nline = realloc(F->set, + nsize * sizeof(LINE))) == NULL) + enomem(); + F->set = nline; + F->setalloc = nsize; + memset(F->set + cnt, 0, + (F->setalloc - cnt) * sizeof(LINE)); + } + + /* + * Get any pushed back line, else get the next line. Allocate + * space as necessary. If taking the line from the stack swap + * the two structures so that we don't lose the allocated space. + * This could be avoided by doing another level of indirection, + * but it's probably okay as is. + */ + lp = &F->set[F->setcnt]; + if (F->pushback != (u_long)-1) { + tmp = F->set[F->setcnt]; + F->set[F->setcnt] = F->set[F->pushback]; + F->set[F->pushback] = tmp; + F->pushback = (u_long)-1; + continue; + } + if ((bp = fgetln(F->fp, &len)) == NULL) + return; + if (lp->linealloc <= len + 1) { + char *n; + + if (lp->linealloc == 0) + nsize = 128; + else + nsize = lp->linealloc; + while (nsize <= len + 1) + nsize <<= 1; + if ((n = realloc(lp->line, + nsize * sizeof(char))) == NULL) + enomem(); + lp->line = n; + lp->linealloc = nsize; + } + memmove(lp->line, bp, len); + + /* Replace trailing newline, if it exists. */ + if (bp[len - 1] == '\n') + lp->line[len - 1] = '\0'; + else + lp->line[len] = '\0'; + bp = lp->line; + + /* Split the line into fields, allocate space as necessary. */ + lp->fieldcnt = 0; + while ((fieldp = strsep(&bp, tabchar)) != NULL) { + if (spans && *fieldp == '\0') + continue; + if (lp->fieldcnt == lp->fieldalloc) { + char **n; + + if (lp->fieldalloc == 0) + nsize = 16; + else + nsize = lp->fieldalloc << 1; + if ((n = realloc(lp->fields, + nsize * sizeof(char *))) == NULL) + enomem(); + lp->fields = n; + lp->fieldalloc = nsize; + } + lp->fields[lp->fieldcnt++] = fieldp; + } + + /* See if the join field value has changed. */ + if (F->setcnt && cmp(lp, F->joinf, lp - 1, F->joinf)) { + F->pushback = F->setcnt; + break; + } + } +} + +static int +cmp(LINE *lp1, u_long fieldno1, LINE *lp2, u_long fieldno2) +{ + + if (lp1->fieldcnt <= fieldno1) + return (lp2->fieldcnt <= fieldno2 ? 0 : 1); + if (lp2->fieldcnt <= fieldno2) + return (-1); + return (strcmp(lp1->fields[fieldno1], lp2->fields[fieldno2])); +} + +static void +joinlines(INPUT *F1, INPUT *F2) +{ + u_long cnt1, cnt2; + + /* + * Output the results of a join comparison. The output may be from + * either file 1 or file 2 (in which case the first argument is the + * file from which to output) or from both. + */ + if (F2 == NULL) { + for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1) + outoneline(F1, &F1->set[cnt1]); + return; + } + for (cnt1 = 0; cnt1 < F1->setcnt; ++cnt1) + for (cnt2 = 0; cnt2 < F2->setcnt; ++cnt2) + outtwoline(F1, &F1->set[cnt1], F2, &F2->set[cnt2]); +} + +static void +outoneline(INPUT *F, LINE *lp) +{ + u_long cnt; + + /* + * Output a single line from one of the files, according to the + * join rules. This happens when we are writing unmatched single + * lines. Output empty fields in the right places. + */ + if (olist) + for (cnt = 0; cnt < olistcnt; ++cnt) { + if (olist[cnt].fileno == (u_long)F->number) + outfield(lp, olist[cnt].fieldno); + else + outfield(&noline, 1); + } + else + for (cnt = 0; cnt < lp->fieldcnt; ++cnt) + outfield(lp, cnt); + (void)printf("\n"); + if (ferror(stdout)) + err(1, "stdout"); + needsep = 0; +} + +static void +outtwoline(INPUT *F1, LINE *lp1, INPUT *F2, LINE *lp2) +{ + u_long cnt; + + /* Output a pair of lines according to the join list (if any). */ + if (olist) { + for (cnt = 0; cnt < olistcnt; ++cnt) + if (olist[cnt].fileno == 1) + outfield(lp1, olist[cnt].fieldno); + else /* if (olist[cnt].fileno == 2) */ + outfield(lp2, olist[cnt].fieldno); + } else { + /* + * Output the join field, then the remaining fields from F1 + * and F2. + */ + outfield(lp1, F1->joinf); + for (cnt = 0; cnt < lp1->fieldcnt; ++cnt) + if (F1->joinf != cnt) + outfield(lp1, cnt); + for (cnt = 0; cnt < lp2->fieldcnt; ++cnt) + if (F2->joinf != cnt) + outfield(lp2, cnt); + } + (void)printf("\n"); + if (ferror(stdout)) + err(1, "stdout"); + needsep = 0; +} + +static void +outfield(LINE *lp, u_long fieldno) +{ + if (needsep++) + (void)printf("%c", *tabchar); + if (!ferror(stdout)) { + if (lp->fieldcnt <= fieldno) { + if (empty != NULL) + (void)printf("%s", empty); + } else { + if (*lp->fields[fieldno] == '\0') + return; + (void)printf("%s", lp->fields[fieldno]); + } + } + if (ferror(stdout)) + err(1, "stdout"); +} + +/* + * Convert an output list argument "2.1, 1.3, 2.4" into an array of output + * fields. + */ +static void +fieldarg(char *option) +{ + u_long fieldno; + char *end, *token; + OLIST *n; + + while ((token = strsep(&option, ", \t")) != NULL) { + if (*token == '\0') + continue; + if ((token[0] != '1' && token[0] != '2') || token[1] != '.') + errx(1, "malformed -o option field"); + fieldno = strtol(token + 2, &end, 10); + if (*end) + errx(1, "malformed -o option field"); + if (fieldno == 0) + errx(1, "field numbers are 1 based"); + if (olistcnt == olistalloc) { + if ((n = realloc(olist, + (olistalloc + 50) * sizeof(OLIST))) == NULL) + enomem(); + olist = n; + olistalloc += 50; + } + olist[olistcnt].fileno = token[0] - '0'; + olist[olistcnt].fieldno = fieldno - 1; + ++olistcnt; + } +} + +static void +obsolete(char **argv) +{ + size_t len; + char **p, *ap, *t; + + while ((ap = *++argv) != NULL) { + /* Return if "--". */ + if (ap[0] == '-' && ap[1] == '-') + return; + switch (ap[1]) { + case 'a': + /* + * The original join allowed "-a", which meant the + * same as -a1 plus -a2. POSIX 1003.2, Draft 11.2 + * only specifies this as "-a 1" and "a -2", so we + * have to use another option flag, one that is + * unlikely to ever be used or accidentally entered + * on the command line. (Well, we could reallocate + * the argv array, but that hardly seems worthwhile.) + */ + if (ap[2] == '\0') + ap[1] = '\01'; + break; + case 'j': + /* + * The original join allowed "-j[12] arg" and "-j arg". + * Convert the former to "-[12] arg". Don't convert + * the latter since getopt(3) can handle it. + */ + switch(ap[2]) { + case '1': + if (ap[3] != '\0') + goto jbad; + ap[1] = '1'; + ap[2] = '\0'; + break; + case '2': + if (ap[3] != '\0') + goto jbad; + ap[1] = '2'; + ap[2] = '\0'; + break; + case '\0': + break; + default: +jbad: errx(1, "illegal option -- %s", ap); + usage(); + } + break; + case 'o': + /* + * The original join allowed "-o arg arg". Convert to + * "-o arg -o arg". + */ + if (ap[2] != '\0') + break; + for (p = argv + 2; *p; ++p) { + if ((p[0][0] != '1' && p[0][0] != '2') || + p[0][1] != '.') + break; + len = strlen(*p); + if (len - 2 != strspn(*p + 2, "0123456789")) + break; + if ((t = malloc(len + 3)) == NULL) + enomem(); + t[0] = '-'; + t[1] = 'o'; + memmove(t + 2, *p, len + 1); + *p = t; + } + argv = p - 1; + break; + } + } +} + +static void +enomem(void) +{ + errx(1, "no memory"); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-a fileno | -v fileno] [-e string] [-j fileno field]\n" + " [-o list] [-t char] [-1 field] [-2 field] file1 file2\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/ldd/Makefile.common b/usr.bin/ldd/Makefile.common new file mode 100644 index 0000000..6193b71 --- /dev/null +++ b/usr.bin/ldd/Makefile.common @@ -0,0 +1,16 @@ +# $NetBSD: Makefile.common,v 1.3 2013/04/25 07:12:46 matt Exp $ + +LDELFSO=${NETBSDSRCDIR}/libexec/ld.elf_so +CPPFLAGS+= -I${LDELFSO} -DLIBDIR=\"${LIBDIR}\" +CPPFLAGS+= -D_RTLD_SOURCE +.if !empty(MACHINE_ARCH:Mearm*) +CPPFLAGS+= -I${NETBSDSRCDIR}/lib/libexecinfo +.endif +#CPPFLAGS+= -DDEBUG +.PATH: ${LDELFSO} + +.if (${MACHINE_ARCH} == "sparc") || (${MACHINE_ARCH} == "sparc64") || \ + (${MACHINE_ARCH} == "arm") || (${MACHINE_ARCH} == "m68k") || \ + (${MACHINE_ARCH} == "powerpc") || (${MACHINE_ARCH:Mmips*} != "") +CPPFLAGS+= -DVARPSZ +.endif diff --git a/usr.bin/ldd/Makefile.elf b/usr.bin/ldd/Makefile.elf new file mode 100644 index 0000000..3acc9a9 --- /dev/null +++ b/usr.bin/ldd/Makefile.elf @@ -0,0 +1,10 @@ +# $NetBSD: Makefile.elf,v 1.5 2013/05/07 13:00:35 christos Exp $ + +# Makefile fragment to build a (32 or 64 bit) libldd_elfxx.a. +# Expects CPPFLAGS to have ELFSIZE set, and LIB to be set. + +SRCS= ldd_elfxx.c +SRCS+= xmalloc.c debug.c expand.c map_object.c load.c search.c \ + headers.c paths.c tls.c symver.c + +.include "Makefile.common" diff --git a/usr.bin/ldd/build/Makefile b/usr.bin/ldd/build/Makefile new file mode 100644 index 0000000..312eb00 --- /dev/null +++ b/usr.bin/ldd/build/Makefile @@ -0,0 +1,42 @@ +# $NetBSD: Makefile,v 1.5 2019/01/27 05:17:48 kre Exp $ + +NOSANITIZER= # defined + +.include # for MKDYNAMICROOT definition + +PROG= ldd +SRCS= ldd.c +MAN= ldd.1 + +.PATH: ${.CURDIR}/.. + +.if ${OBJECT_FMTS:Melf32} != "" +LIB_ELF32DIR!= cd ${.CURDIR}/../elf32 && ${PRINTOBJDIR} +EXTRA_LIBS+= ${LIB_ELF32DIR}/libldd_elf32.a +.else +CPPFLAGS.ldd.c+= -DELF64_ONLY +.endif + +.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb") +LIB_ELF32COMPATDIR!= cd ${.CURDIR}/../elf32_compat && ${PRINTOBJDIR} +EXTRA_LIBS+= ${LIB_ELF32COMPATDIR}/libldd_elf32_compat.a +.endif + +.if ${OBJECT_FMTS:Melf64} != "" +LIB_ELF64DIR!= cd ${.CURDIR}/../elf64 && ${PRINTOBJDIR} +EXTRA_LIBS+= ${LIB_ELF64DIR}/libldd_elf64.a +CPPFLAGS.ldd.c+= -DELFSIZE=64 +.else +CPPFLAGS.ldd.c+= -DELFSIZE=32 +.endif + +LDADD+= ${EXTRA_LIBS} +DPADD+= ${EXTRA_LIBS} + +.include "Makefile.common" + +.if (${MKDYNAMICROOT} == "no") +LDSTATIC?= -static +.endif + +.include diff --git a/usr.bin/ldd/dummy.c b/usr.bin/ldd/dummy.c new file mode 100644 index 0000000..51f4a6e --- /dev/null +++ b/usr.bin/ldd/dummy.c @@ -0,0 +1,3 @@ +/* $NetBSD: dummy.c,v 1.1 2009/01/07 00:39:24 mrg Exp $ */ + +/* This file left intentially blank. */ diff --git a/usr.bin/ldd/elf32/Makefile b/usr.bin/ldd/elf32/Makefile new file mode 100644 index 0000000..71e00b2 --- /dev/null +++ b/usr.bin/ldd/elf32/Makefile @@ -0,0 +1,33 @@ +# $NetBSD: Makefile,v 1.12 2019/01/27 05:16:55 kre Exp $ + +NOSANITIZER= # defined + +.include +.include + +RTLD_FUNCS = \ + _rtld_tls_allocate \ + _rtld_tls_free \ + +.for _d in ${RTLD_FUNCS} +CPPFLAGS+= -D${_d}=_elf32_${_d} +.endfor + +CPPFLAGS+= -DELFSIZE=32 +LIB= ldd_elf32 + +# XXX Force one member +SRCS= dummy.c + +LIBISPRIVATE= yes +.PATH: ${.CURDIR}/.. + +.ifdef MLIBDIR +CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\" +.endif + +.if ${OBJECT_FMTS:Melf32} != "" +.include "../Makefile.elf" +.endif + +.include diff --git a/usr.bin/ldd/elf32_compat/Makefile b/usr.bin/ldd/elf32_compat/Makefile new file mode 100644 index 0000000..e604c20 --- /dev/null +++ b/usr.bin/ldd/elf32_compat/Makefile @@ -0,0 +1,26 @@ +# $NetBSD: Makefile,v 1.4 2019/01/27 05:15:42 kre Exp $ + +NOSANITIZER= # defined + +.include +.include + +CPPFLAGS+= -DELFSIZE=32 -DELF32_COMPAT +LIB= ldd_elf32_compat + +# XXX Force one member +SRCS= dummy.c + +LIBISPRIVATE= yes +.PATH: ${.CURDIR}/.. + +.ifdef COMPAT_MLIBDIR +MLIBDIR= ${COMPAT_MLIBDIR} +CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\" +.endif + +.ifdef MLIBDIR +.include "../Makefile.elf" +.endif + +.include diff --git a/usr.bin/ldd/elf64/Makefile b/usr.bin/ldd/elf64/Makefile new file mode 100644 index 0000000..f5cb2ab --- /dev/null +++ b/usr.bin/ldd/elf64/Makefile @@ -0,0 +1,51 @@ +# $NetBSD: Makefile,v 1.11 2019/01/27 05:14:45 kre Exp $ + +NOSANITIZER= # defined + +.include + +CPPFLAGS+= -DELFSIZE=64 +LIB= ldd_elf64 + +# XXX Force one member +SRCS= dummy.c + +LIBISPRIVATE= yes +.PATH: ${.CURDIR}/.. + +.if ${OBJECT_FMTS:Melf64} != "" + +# XXX we need to make sure that we don't accidentally get the elf32 +# XXX versions of these. + +RTLD_FUNCS = \ + _rtld_expand_path \ + _rtld_digest_dynamic \ + _rtld_digest_phdr \ + _rtld_load_needed_objects \ + _rtld_load_object \ + _rtld_map_object \ + _rtld_obj_free \ + _rtld_obj_new \ + _rtld_object_add_name \ + _rtld_object_match_name \ + _rtld_add_paths \ + _rtld_process_hints \ + _rtld_sysctl \ + _rtld_tls_allocate \ + _rtld_tls_free \ + _rtld_load_library + +.for _d in ${RTLD_FUNCS} +CPPFLAGS+= -D${_d}=_elf64_${_d} +.endfor + +.if (${MACHINE_ARCH} == "mips64el") || (${MACHINE_ARCH} == "mips64eb") +CPPFLAGS+= -DRTLD_ARCH_SUBDIR=\"${MLIBDIR}\" +.endif + +.include "../Makefile.elf" + +.endif + +.include diff --git a/usr.bin/ldd/ldd.1 b/usr.bin/ldd/ldd.1 new file mode 100644 index 0000000..6e49e9c --- /dev/null +++ b/usr.bin/ldd/ldd.1 @@ -0,0 +1,126 @@ +.\" $NetBSD: ldd.1,v 1.20 2017/12/25 05:08:49 maya Exp $ +.\" +.\" Copyright (c) 1998 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Paul Kranenburg. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd December 25, 2017 +.Dt LDD 1 +.Os +.Sh NAME +.Nm ldd +.Nd list dynamic object dependencies +.Sh SYNOPSIS +.Nm +.Op Fl o +.Op Fl f Ar format +.Ar program ... +.Sh DESCRIPTION +.Nm +displays all shared objects that are needed to run the given program. +Contrary to +.Xr nm 1 , +the list includes +.Dq indirect +dependencies that are the result of needed shared objects which themselves +depend on yet other shared objects. +Zero, one or two +.Fl f +options may be given. +The argument is a format string passed to +.Xr rtld 1 +and allows customization of +.Nm ldd Ns 's +output. +The first format argument is used for library objects and defaults to +.Qq "\et-l%o.%m => %p\en" . +The second format argument is used for non-library objects and defaults to +.Qq "\et%o => %p\en" . +.Pp +These arguments are interpreted as format strings a la +.Xr printf 3 +to customize the trace output and allow +.Nm +to be operated as a filter more conveniently. +The following conversions can be used: +.Bl -tag -width xxxx +.It \&%a +The main program's name +.Po also known as +.Dq __progname +.Pc . +.It \&%A +The value of the environment variable +.Ev LD_TRACE_LOADED_OBJECTS_PROGNAME +in a.out and the program name from the argument vector from elf. +.It \&%o +The library name. +.It \&%m +The library's major version number. +.It \&%n +The library's minor version number (a.out only, ignored in elf). +.It \&%p +The full pathname as determined by +.Nm rtld Ns 's +library search rules. +.It \&%x +The library's load address +.El +.Pp +Additionally, +.Sy \en +and +.Sy \et +are recognized and have their usual meaning. +.Pp +The +.Fl o +option is an alias for +.Fl f +.Ar \&%a:-l\&%o.\&%m => \&%p\en , +which makes +.Nm +behave analogously to +.Ic nm Fl o . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr ld 1 , +.Xr ld.elf_so 1 , +.Xr nm 1 , +.Xr rtld 1 +.Sh HISTORY +A +.Nm +utility first appeared in SunOS 4.0, it appeared in its current form +in +.Nx 0.9a . +.Sh BUGS +The +a.out +.Nm +actually runs the program it has been requested to analyze which in specially +constructed environments can have security implications. diff --git a/usr.bin/ldd/ldd.c b/usr.bin/ldd/ldd.c new file mode 100644 index 0000000..a2794f8 --- /dev/null +++ b/usr.bin/ldd/ldd.c @@ -0,0 +1,237 @@ +/* $NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $ */ + +/*- + * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Paul Kranenburg. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright 1996 John D. Polstra. + * Copyright 1996 Matt Thomas + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by John Polstra. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: ldd.c,v 1.23 2017/12/25 05:08:49 maya Exp $"); +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "rtld.h" +#include "ldd.h" + +/* + * Data declarations. + */ +static char *error_message; /* Message for dlopen(), or NULL */ +bool _rtld_trust; /* False for setuid and setgid programs */ +/* + * This may be ELF64 or ELF32 but since they are used opaquely it doesn't + * really matter. + */ +Obj_Entry *_rtld_objlist; /* Head of linked list of shared objects */ +Obj_Entry **_rtld_objtail = &_rtld_objlist; + /* Link field of last object in list */ +u_int _rtld_objcount; /* Number of shared objects */ +u_int _rtld_objloads; /* Number of objects loaded */ + +Obj_Entry *_rtld_objmain; /* The main program shared object */ +size_t _rtld_pagesz; + +Search_Path *_rtld_default_paths; +Search_Path *_rtld_paths; +Library_Xform *_rtld_xforms; + +static void usage(void) __dead; +char *main_local; +char *main_progname; + +static void +usage(void) +{ + fprintf(stderr, "Usage: %s [-f ] [-f ] " + " ...\n", getprogname()); + exit(1); +} + +int +main(int argc, char **argv) +{ + const char *fmt1 = NULL, *fmt2 = NULL; + int c, exit_status = EXIT_SUCCESS; + +#ifdef DEBUG + debug = 1; +#endif + while ((c = getopt(argc, argv, "f:o")) != -1) { + switch (c) { + case 'f': + if (fmt1) { + if (fmt2) + errx(1, "Too many formats"); + fmt2 = optarg; + } else + fmt1 = optarg; + break; + case 'o': + if (fmt1 || fmt2) + errx(1, "Cannot use -o and -f together"); + fmt1 = "%a:-l%o.%m => %p\n"; + break; + default: + usage(); + /*NOTREACHED*/ + } + } + argc -= optind; + argv += optind; + + if (argc <= 0) { + usage(); + /*NOTREACHED*/ + } + + for (; argc != 0; argc--, argv++) { + int fd; + + fd = open(*argv, O_RDONLY); + if (fd == -1) { + exit_status = EXIT_FAILURE; + warn("%s", *argv); + continue; + } + if (elf_ldd(fd, *argv, fmt1, fmt2) == -1 + /* Alpha never had 32 bit support. */ +#if (defined(_LP64) && !defined(ELF64_ONLY)) || defined(MIPS_N32) + && elf32_ldd(fd, *argv, fmt1, fmt2) == -1 +#if defined(__mips__) && 0 /* XXX this is still hosed for some reason */ + && elf32_ldd_compat(fd, *argv, fmt1, fmt2) == -1 +#endif +#endif + ) { + exit_status = EXIT_FAILURE; + warnx("%s", error_message); + } + close(fd); + } + + return exit_status; +} + +/* + * Error reporting function. Use it like printf. If formats the message + * into a buffer, and sets things up so that the next call to dlerror() + * will return the message. + */ +void +_rtld_error(const char *fmt, ...) +{ + static char buf[512]; + va_list ap; + va_start(ap, fmt); + xvsnprintf(buf, sizeof buf, fmt, ap); + error_message = buf; + va_end(ap); +} + +char * +dlerror() +{ + char *msg = error_message; + error_message = NULL; + return msg; +} + +void +_rtld_die(void) +{ + const char *msg = dlerror(); + + if (msg == NULL) + msg = "Fatal error"; + xerrx(1, "%s", msg); +} + +void +_rtld_shared_enter(void) +{ +} + +void +_rtld_shared_exit(void) +{ +} + +void +_rtld_exclusive_enter(sigset_t *mask) +{ +} + +void +_rtld_exclusive_exit(sigset_t *mask) +{ +} diff --git a/usr.bin/ldd/ldd.h b/usr.bin/ldd/ldd.h new file mode 100644 index 0000000..a3b4520 --- /dev/null +++ b/usr.bin/ldd/ldd.h @@ -0,0 +1,47 @@ +/* $NetBSD: ldd.h,v 1.7 2012/07/08 00:53:44 matt Exp $ */ + +/* + * Copyright (c) 2008 Matthew R. Green + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +int elf32_ldd(int, char *, const char *, const char *); + +#ifdef _LP64 +#define LDD_ELF64 +#endif + +#ifdef LDD_ELF64 +int elf64_ldd(int, char *, const char *, const char *); +#define elf_ldd elf64_ldd +#elif defined(ELF32_COMPAT) +#define elf_ldd elf32_compat_ldd +#else +#define elf_ldd elf32_ldd +#endif + +extern char *main_local; +extern char *main_progname; diff --git a/usr.bin/ldd/ldd_elfxx.c b/usr.bin/ldd/ldd_elfxx.c new file mode 100644 index 0000000..d5def64 --- /dev/null +++ b/usr.bin/ldd/ldd_elfxx.c @@ -0,0 +1,269 @@ +/* $NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $ */ + +/*- + * Copyright (c) 1998, 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Paul Kranenburg. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Copyright 1996 John D. Polstra. + * Copyright 1996 Matt Thomas + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by John Polstra. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: ldd_elfxx.c,v 1.7 2017/01/10 21:11:25 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "debug.h" +#include "rtld.h" +#include "ldd.h" + +static void print_needed(Obj_Entry *, const char *, const char *); +static void fmtprint(const char *, Obj_Entry *, const char *, const char *); + +/* + * elfxx_ldd() - bit-size independent ELF ldd implementation. + * returns 0 on success and -1 on failure. + */ +int +ELFNAME(ldd)(int fd, char *path, const char *fmt1, const char *fmt2) +{ + struct stat st; + + if (lseek(fd, 0, SEEK_SET) < 0 || + fstat(fd, &st) < 0) { + _rtld_error("%s: %s", path, strerror(errno)); + return -1; + } + + _rtld_pagesz = sysconf(_SC_PAGESIZE); + +#ifdef RTLD_ARCH_SUBDIR + _rtld_add_paths(path, &_rtld_default_paths, + RTLD_DEFAULT_LIBRARY_PATH "/" RTLD_ARCH_SUBDIR); +#endif + _rtld_add_paths(path, &_rtld_default_paths, RTLD_DEFAULT_LIBRARY_PATH); + + _rtld_paths = NULL; + _rtld_trust = (st.st_mode & (S_ISUID | S_ISGID)) == 0; + if (_rtld_trust) + _rtld_add_paths(path, &_rtld_paths, getenv("LD_LIBRARY_PATH")); + + _rtld_process_hints(path, &_rtld_paths, &_rtld_xforms, _PATH_LD_HINTS); + _rtld_objmain = _rtld_map_object(xstrdup(path), fd, &st); + if (_rtld_objmain == NULL) + return -1; + + _rtld_objmain->path = xstrdup(path); + _rtld_digest_dynamic(path, _rtld_objmain); + + /* Link the main program into the list of objects. */ + *_rtld_objtail = _rtld_objmain; + _rtld_objtail = &_rtld_objmain->next; + ++_rtld_objmain->refcount; + + (void) _rtld_load_needed_objects(_rtld_objmain, 0); + + if (fmt1 == NULL) + printf("%s:\n", _rtld_objmain->path); + main_local = path; + main_progname = _rtld_objmain->path; + print_needed(_rtld_objmain, fmt1, fmt2); + + while (_rtld_objlist != NULL) { + Obj_Entry *obj = _rtld_objlist; + _rtld_objlist = obj->next; + while (obj->rpaths != NULL) { + const Search_Path *rpath = obj->rpaths; + obj->rpaths = rpath->sp_next; + xfree(__UNCONST(rpath->sp_path)); + xfree(__UNCONST(rpath)); + } + while (obj->needed != NULL) { + const Needed_Entry *needed = obj->needed; + obj->needed = needed->next; + xfree(__UNCONST(needed)); + } + (void) munmap(obj->mapbase, obj->mapsize); + xfree(obj->path); + xfree(obj); + } + + _rtld_objmain = NULL; + _rtld_objtail = &_rtld_objlist; + /* Need to free _rtld_paths? */ + + return 0; +} + +void +fmtprint(const char *libname, Obj_Entry *obj, const char *fmt1, + const char *fmt2) +{ + const char *libpath = obj ? obj->path : "not found"; + char libnamebuf[200]; + char *libmajor = NULL; + const char *fmt; + char *cp; + int c; + + if (strncmp(libname, "lib", 3) == 0 && + (cp = strstr(libname, ".so")) != NULL) { + size_t i = cp - (libname + 3); + + if (i >= sizeof(libnamebuf)) + i = sizeof(libnamebuf) - 1; + (void)memcpy(libnamebuf, libname + 3, i); + libnamebuf[i] = '\0'; + if (cp[3] && isdigit((unsigned char)cp[4])) + libmajor = &cp[4]; + libname = libnamebuf; + } + + if (fmt1 == NULL) + fmt1 = libmajor != NULL ? + "\t-l%o.%m => %p\n" : + "\t-l%o => %p\n"; + if (fmt2 == NULL) + fmt2 = "\t%o => %p\n"; + + fmt = libname == libnamebuf ? fmt1 : fmt2; + while ((c = *fmt++) != '\0') { + switch (c) { + default: + putchar(c); + continue; + case '\\': + switch (c = *fmt) { + case '\0': + continue; + case 'n': + putchar('\n'); + break; + case 't': + putchar('\t'); + break; + } + break; + case '%': + switch (c = *fmt) { + case '\0': + continue; + case '%': + default: + putchar(c); + break; + case 'A': + printf("%s", main_local); + break; + case 'a': + printf("%s", main_progname); + break; + case 'o': + printf("%s", libname); + break; + case 'm': + printf("%s", libmajor); + break; + case 'n': + /* XXX: not supported for elf */ + break; + case 'p': + printf("%s", libpath); + break; + case 'x': + printf("%p", obj ? obj->mapbase : 0); + break; + } + break; + } + ++fmt; + } +} + +void +print_needed(Obj_Entry *obj, const char *fmt1, const char *fmt2) +{ + const Needed_Entry *needed; + + for (needed = obj->needed; needed != NULL; needed = needed->next) { + const char *libname = obj->strtab + needed->name; + + if (needed->obj != NULL) { + if (!needed->obj->printed) { + fmtprint(libname, needed->obj, fmt1, fmt2); + needed->obj->printed = 1; + print_needed(needed->obj, fmt1, fmt2); + } + } else { + fmtprint(libname, needed->obj, fmt1, fmt2); + } + } +} diff --git a/usr.bin/locale/locale.1 b/usr.bin/locale/locale.1 new file mode 100644 index 0000000..5cd4b0e --- /dev/null +++ b/usr.bin/locale/locale.1 @@ -0,0 +1,113 @@ +.\" $NetBSD: locale.1,v 1.2 2003/07/04 07:28:14 wiz Exp $ +.\" +.\" Copyright (c) 2003 Alexey Zelkin +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" FreeBSD: src/usr.bin/locale/locale.1,v 1.3 2003/06/26 11:01:03 phantom Exp +.\" +.Dd July 4, 2003 +.Dt LOCALE 1 +.Os +.Sh NAME +.Nm locale +.Nd get locale-specific information +.Sh SYNOPSIS +.Nm +.Op Fl a | Fl m +.Nm +.Op Fl ck +.Op Ar keyword ... +.Sh DESCRIPTION +The +.Nm +utility is supposed to provide most locale specific information to +the standard output. +.Pp +When +.Nm +is invoked without arguments it will print out a summary of the +current locale environment depending on environment variable settings +and internal status. +.Pp +When +.Nm +is invoked with arguments and no options specified it will print out +.Em keyword Ns No 's +value determined using current locale settings. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl a +Write names of all available locales. +While looking for locales +.Nm +will respect the +.Ev PATH_LOCALE +environment variable, and use it instead of the system default +locale directory. +.It Fl c +Write the category name for the selected keywords. +.It Fl k +Write the name and value of the selected keywords. +.It Fl m +Write names of all available charmaps. +.El +.Sh IMPLEMENTATION DETAILS +Special +.Pf ( Fx Ns - +/ +.Nx Ns -specific ) +keyword +.Ar list +can be used to retrieve a human readable list of available keywords. +.Sh DIAGNOSTICS +.Ex -std locale +.Sh STANDARDS +.Nm +conforms to +.St -p1003.1-2001 . +.Sh HISTORY +.Nm +first appeared in +.Nx 2.0 . +.Sh AUTHORS +This implementation of +.Nm +was originally written by +.An Alexey Zelkin +.Aq phantom@FreeBSD.org +for +.Fx . +.Sh BUGS +Since +.Nx +does not support +.Em charmap Ns No s +in their +.Em POSIX +meaning +.Nm +emulates the +.Fl m +option via CODESETs listing of all available locales. diff --git a/usr.bin/locale/locale.c b/usr.bin/locale/locale.c new file mode 100644 index 0000000..bd02d53 --- /dev/null +++ b/usr.bin/locale/locale.c @@ -0,0 +1,707 @@ +/* $NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $ */ + +/*- + * Copyright (c) 2002, 2003 Alexey Zelkin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +__RCSID("$NetBSD: locale.c,v 1.8 2012/01/20 16:31:30 joerg Exp $"); +#endif /* LIBC_SCCS and not lint */ + +/* + * XXX: implement missing era_* (LC_TIME) keywords (require libc & + * nl_langinfo(3) extensions) + * + * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require + * localedef(1) implementation). Currently it's handled via + * nl_langinfo(CODESET). + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "citrus_namespace.h" +#include "citrus_region.h" +#include "citrus_lookup.h" +#include "setlocale_local.h" + +/* Local prototypes */ +void init_locales_list(void); +void init_locales_list_alias(void); +void list_charmaps(void); +void list_locales(void); +const char *lookup_localecat(int); +char *kwval_lconv(int); +int kwval_lookup(char *, char **, int *, int *); +void showdetails(char *); +void showkeywordslist(void); +void showlocale(void); +void usage(void); + +/* Global variables */ +static StringList *locales = NULL; + +int all_locales = 0; +int all_charmaps = 0; +int prt_categories = 0; +int prt_keywords = 0; +int more_params = 0; + +struct _lcinfo { + const char *name; + int id; +} lcinfo [] = { + { "LC_CTYPE", LC_CTYPE }, + { "LC_COLLATE", LC_COLLATE }, + { "LC_TIME", LC_TIME }, + { "LC_NUMERIC", LC_NUMERIC }, + { "LC_MONETARY", LC_MONETARY }, + { "LC_MESSAGES", LC_MESSAGES } +}; +#define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0])) + +/* ids for values not referenced by nl_langinfo() */ +#define KW_ZERO 10000 +#define KW_GROUPING (KW_ZERO+1) +#define KW_INT_CURR_SYMBOL (KW_ZERO+2) +#define KW_CURRENCY_SYMBOL (KW_ZERO+3) +#define KW_MON_DECIMAL_POINT (KW_ZERO+4) +#define KW_MON_THOUSANDS_SEP (KW_ZERO+5) +#define KW_MON_GROUPING (KW_ZERO+6) +#define KW_POSITIVE_SIGN (KW_ZERO+7) +#define KW_NEGATIVE_SIGN (KW_ZERO+8) +#define KW_INT_FRAC_DIGITS (KW_ZERO+9) +#define KW_FRAC_DIGITS (KW_ZERO+10) +#define KW_P_CS_PRECEDES (KW_ZERO+11) +#define KW_P_SEP_BY_SPACE (KW_ZERO+12) +#define KW_N_CS_PRECEDES (KW_ZERO+13) +#define KW_N_SEP_BY_SPACE (KW_ZERO+14) +#define KW_P_SIGN_POSN (KW_ZERO+15) +#define KW_N_SIGN_POSN (KW_ZERO+16) +#define KW_INT_P_CS_PRECEDES (KW_ZERO+17) +#define KW_INT_P_SEP_BY_SPACE (KW_ZERO+18) +#define KW_INT_N_CS_PRECEDES (KW_ZERO+19) +#define KW_INT_N_SEP_BY_SPACE (KW_ZERO+20) +#define KW_INT_P_SIGN_POSN (KW_ZERO+21) +#define KW_INT_N_SIGN_POSN (KW_ZERO+22) + +struct _kwinfo { + const char *name; + int isstr; /* true - string, false - number */ + int catid; /* LC_* */ + int value_ref; + const char *comment; +} kwinfo [] = { + { "charmap", 1, LC_CTYPE, CODESET, "" }, /* hack */ + + { "decimal_point", 1, LC_NUMERIC, RADIXCHAR, "" }, + { "thousands_sep", 1, LC_NUMERIC, THOUSEP, "" }, + { "grouping", 1, LC_NUMERIC, KW_GROUPING, "" }, + { "radixchar", 1, LC_NUMERIC, RADIXCHAR, + "Same as decimal_point (BSD only)" }, /* compat */ + { "thousep", 1, LC_NUMERIC, THOUSEP, + "Same as thousands_sep (BSD only)" }, /* compat */ + + { "int_curr_symbol", 1, LC_MONETARY, KW_INT_CURR_SYMBOL, "" }, + { "currency_symbol", 1, LC_MONETARY, KW_CURRENCY_SYMBOL, "" }, + { "mon_decimal_point", 1, LC_MONETARY, KW_MON_DECIMAL_POINT, "" }, + { "mon_thousands_sep", 1, LC_MONETARY, KW_MON_THOUSANDS_SEP, "" }, + { "mon_grouping", 1, LC_MONETARY, KW_MON_GROUPING, "" }, + { "positive_sign", 1, LC_MONETARY, KW_POSITIVE_SIGN, "" }, + { "negative_sign", 1, LC_MONETARY, KW_NEGATIVE_SIGN, "" }, + + { "int_frac_digits", 0, LC_MONETARY, KW_INT_FRAC_DIGITS, "" }, + { "frac_digits", 0, LC_MONETARY, KW_FRAC_DIGITS, "" }, + { "p_cs_precedes", 0, LC_MONETARY, KW_P_CS_PRECEDES, "" }, + { "p_sep_by_space", 0, LC_MONETARY, KW_P_SEP_BY_SPACE, "" }, + { "n_cs_precedes", 0, LC_MONETARY, KW_N_CS_PRECEDES, "" }, + { "n_sep_by_space", 0, LC_MONETARY, KW_N_SEP_BY_SPACE, "" }, + { "p_sign_posn", 0, LC_MONETARY, KW_P_SIGN_POSN, "" }, + { "n_sign_posn", 0, LC_MONETARY, KW_N_SIGN_POSN, "" }, + { "int_p_cs_precedes", 0, LC_MONETARY, KW_INT_P_CS_PRECEDES, "" }, + { "int_p_sep_by_space", 0, LC_MONETARY, KW_INT_P_SEP_BY_SPACE, "" }, + { "int_n_cs_precedes", 0, LC_MONETARY, KW_INT_N_CS_PRECEDES, "" }, + { "int_n_sep_by_space", 0, LC_MONETARY, KW_INT_N_SEP_BY_SPACE, "" }, + { "int_p_sign_posn", 0, LC_MONETARY, KW_INT_P_SIGN_POSN, "" }, + { "int_n_sign_posn", 0, LC_MONETARY, KW_INT_N_SIGN_POSN, "" }, + + { "d_t_fmt", 1, LC_TIME, D_T_FMT, "" }, + { "d_fmt", 1, LC_TIME, D_FMT, "" }, + { "t_fmt", 1, LC_TIME, T_FMT, "" }, + { "am_str", 1, LC_TIME, AM_STR, "" }, + { "pm_str", 1, LC_TIME, PM_STR, "" }, + { "t_fmt_ampm", 1, LC_TIME, T_FMT_AMPM, "" }, + { "day_1", 1, LC_TIME, DAY_1, "" }, + { "day_2", 1, LC_TIME, DAY_2, "" }, + { "day_3", 1, LC_TIME, DAY_3, "" }, + { "day_4", 1, LC_TIME, DAY_4, "" }, + { "day_5", 1, LC_TIME, DAY_5, "" }, + { "day_6", 1, LC_TIME, DAY_6, "" }, + { "day_7", 1, LC_TIME, DAY_7, "" }, + { "abday_1", 1, LC_TIME, ABDAY_1, "" }, + { "abday_2", 1, LC_TIME, ABDAY_2, "" }, + { "abday_3", 1, LC_TIME, ABDAY_3, "" }, + { "abday_4", 1, LC_TIME, ABDAY_4, "" }, + { "abday_5", 1, LC_TIME, ABDAY_5, "" }, + { "abday_6", 1, LC_TIME, ABDAY_6, "" }, + { "abday_7", 1, LC_TIME, ABDAY_7, "" }, + { "mon_1", 1, LC_TIME, MON_1, "" }, + { "mon_2", 1, LC_TIME, MON_2, "" }, + { "mon_3", 1, LC_TIME, MON_3, "" }, + { "mon_4", 1, LC_TIME, MON_4, "" }, + { "mon_5", 1, LC_TIME, MON_5, "" }, + { "mon_6", 1, LC_TIME, MON_6, "" }, + { "mon_7", 1, LC_TIME, MON_7, "" }, + { "mon_8", 1, LC_TIME, MON_8, "" }, + { "mon_9", 1, LC_TIME, MON_9, "" }, + { "mon_10", 1, LC_TIME, MON_10, "" }, + { "mon_11", 1, LC_TIME, MON_11, "" }, + { "mon_12", 1, LC_TIME, MON_12, "" }, + { "abmon_1", 1, LC_TIME, ABMON_1, "" }, + { "abmon_2", 1, LC_TIME, ABMON_2, "" }, + { "abmon_3", 1, LC_TIME, ABMON_3, "" }, + { "abmon_4", 1, LC_TIME, ABMON_4, "" }, + { "abmon_5", 1, LC_TIME, ABMON_5, "" }, + { "abmon_6", 1, LC_TIME, ABMON_6, "" }, + { "abmon_7", 1, LC_TIME, ABMON_7, "" }, + { "abmon_8", 1, LC_TIME, ABMON_8, "" }, + { "abmon_9", 1, LC_TIME, ABMON_9, "" }, + { "abmon_10", 1, LC_TIME, ABMON_10, "" }, + { "abmon_11", 1, LC_TIME, ABMON_11, "" }, + { "abmon_12", 1, LC_TIME, ABMON_12, "" }, + { "era", 1, LC_TIME, ERA, "(unavailable)" }, + { "era_d_fmt", 1, LC_TIME, ERA_D_FMT, "(unavailable)" }, + { "era_d_t_fmt", 1, LC_TIME, ERA_D_T_FMT, "(unavailable)" }, + { "era_t_fmt", 1, LC_TIME, ERA_T_FMT, "(unavailable)" }, + { "alt_digits", 1, LC_TIME, ALT_DIGITS, "" }, + + { "yesexpr", 1, LC_MESSAGES, YESEXPR, "" }, + { "noexpr", 1, LC_MESSAGES, NOEXPR, "" }, + { "yesstr", 1, LC_MESSAGES, YESSTR, + "(POSIX legacy)" }, /* compat */ + { "nostr", 1, LC_MESSAGES, NOSTR, + "(POSIX legacy)" } /* compat */ + +}; +#define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0])) + +int +main(int argc, char *argv[]) +{ + int ch; + int tmp; + + while ((ch = getopt(argc, argv, "ackm")) != -1) { + switch (ch) { + case 'a': + all_locales = 1; + break; + case 'c': + prt_categories = 1; + break; + case 'k': + prt_keywords = 1; + break; + case 'm': + all_charmaps = 1; + break; + default: + usage(); + } + } + argc -= optind; + argv += optind; + + /* validate arguments */ + if (all_locales && all_charmaps) + usage(); + if ((all_locales || all_charmaps) && argc > 0) + usage(); + if ((all_locales || all_charmaps) && (prt_categories || prt_keywords)) + usage(); + if ((prt_categories || prt_keywords) && argc <= 0) + usage(); + + /* process '-a' */ + if (all_locales) { + list_locales(); + exit(0); + } + + /* process '-m' */ + if (all_charmaps) { + list_charmaps(); + exit(0); + } + + /* check for special case '-k list' */ + tmp = 0; + if (prt_keywords && argc > 0) + while (tmp < argc) + if (strcasecmp(argv[tmp++], "list") == 0) { + showkeywordslist(); + exit(0); + } + + /* process '-c' and/or '-k' */ + if (prt_categories || prt_keywords || argc > 0) { + setlocale(LC_ALL, ""); + while (argc > 0) { + showdetails(*argv); + argv++; + argc--; + } + exit(0); + } + + /* no arguments, show current locale state */ + showlocale(); + + return (0); +} + +void +usage(void) +{ + printf("usage: locale [ -a | -m ]\n" + " locale [ -ck ] name ...\n"); + exit(1); +} + +/* + * Output information about all available locales + * + * XXX actually output of this function does not guarantee that locale + * is really available to application, since it can be broken or + * inconsistent thus setlocale() will fail. Maybe add '-V' function to + * also validate these locales? + */ +void +list_locales(void) +{ + size_t i; + + init_locales_list(); + for (i = 0; i < locales->sl_cur; i++) { + printf("%s\n", locales->sl_str[i]); + } +} + +/* + * qsort() helper function + */ +static int +scmp(const void *s1, const void *s2) +{ + return strcmp(*(const char **)s1, *(const char **)s2); +} + +/* + * Output information about all available charmaps + * + * XXX this function is doing a task in hackish way, i.e. by scaning + * list of locales, spliting their codeset part and building list of + * them. + */ +void +list_charmaps(void) +{ + size_t i; + char *s, *cs; + StringList *charmaps; + + /* initialize StringList */ + charmaps = sl_init(); + if (charmaps == NULL) + err(1, "could not allocate memory"); + + /* fetch locales list */ + init_locales_list(); + + /* split codesets and build their list */ + for (i = 0; i < locales->sl_cur; i++) { + s = locales->sl_str[i]; + if ((cs = strchr(s, '.')) != NULL) { + cs++; + if (sl_find(charmaps, cs) == NULL) + sl_add(charmaps, cs); + } + } + + /* add US-ASCII, if not yet added */ + if (sl_find(charmaps, "US-ASCII") == NULL) + sl_add(charmaps, "US-ASCII"); + + /* sort the list */ + qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp); + + /* print results */ + for (i = 0; i < charmaps->sl_cur; i++) { + printf("%s\n", charmaps->sl_str[i]); + } +} + +/* + * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE + * environment variable is set) + */ +void +init_locales_list(void) +{ + DIR *dirp; + struct dirent *dp; + char *s; + + /* why call this function twice ? */ + if (locales != NULL) + return; + + /* initialize StringList */ + locales = sl_init(); + if (locales == NULL) + err(1, "could not allocate memory"); + + /* get actual locales directory name */ + setlocale(LC_CTYPE, "C"); + if (_PathLocale == NULL) + errx(1, "unable to find locales storage"); + + /* open locales directory */ + dirp = opendir(_PathLocale); + if (dirp == NULL) + err(1, "could not open directory '%s'", _PathLocale); + + /* scan directory and store its contents except "." and ".." */ + while ((dp = readdir(dirp)) != NULL) { + /* exclude "." and "..", _LOCALE_ALIAS_NAME */ + if ((dp->d_name[0] != '.' || (dp->d_name[1] != '\0' && + (dp->d_name[1] != '.' || dp->d_name[2] != '\0'))) && + strcmp(_LOCALE_ALIAS_NAME, dp->d_name) != 0) { + s = strdup(dp->d_name); + if (s == NULL) + err(1, "could not allocate memory"); + sl_add(locales, s); + } + } + closedir(dirp); + + /* make sure that 'POSIX' and 'C' locales are present in the list. + * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but + * we also list 'C' for constistency + */ + if (sl_find(locales, "POSIX") == NULL) + sl_add(locales, "POSIX"); + + if (sl_find(locales, "C") == NULL) + sl_add(locales, "C"); + + init_locales_list_alias(); + + /* make output nicer, sort the list */ + qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp); +} + +void +init_locales_list_alias(void) +{ + char aliaspath[PATH_MAX]; + struct _lookup *hlookup; + struct _region key, dat; + size_t n; + char *s, *t; + + _DIAGASSERT(locales != NULL); + _DIAGASSERT(_PathLocale != NULL); + + (void)snprintf(aliaspath, sizeof(aliaspath), + "%s/" _LOCALE_ALIAS_NAME, _PathLocale); + + if (_lookup_seq_open(&hlookup, aliaspath, + _LOOKUP_CASE_SENSITIVE) == 0) { + while (_lookup_seq_next(hlookup, &key, &dat) == 0) { + n = _region_size((const struct _region *)&key); + s = _region_head((const struct _region *)&key); + for (t = s; n > 0 && *s!= '/'; --n, ++s); + n = (size_t)(s - t); + s = malloc(n + 1); + if (s == NULL) + err(1, "could not allocate memory"); + memcpy(s, t, n); + s[n] = '\0'; + if (sl_find(locales, s) == NULL) + sl_add(locales, s); + else + free(s); + } + _lookup_seq_close(hlookup); + } +} + +/* + * Show current locale status, depending on environment variables + */ +void +showlocale(void) +{ + size_t i; + const char *lang, *vval, *eval; + + setlocale(LC_ALL, ""); + + lang = getenv("LANG"); + if (lang == NULL) { + lang = ""; + } + printf("LANG=\"%s\"\n", lang); + /* XXX: if LANG is null, then set it to "C" to get implied values? */ + + for (i = 0; i < NLCINFO; i++) { + vval = setlocale(lcinfo[i].id, NULL); + eval = getenv(lcinfo[i].name); + if (eval != NULL && !strcmp(eval, vval) + && strcmp(lang, vval)) { + /* + * Appropriate environment variable set, its value + * is valid and not overriden by LC_ALL + * + * XXX: possible side effect: if both LANG and + * overriden environment variable are set into same + * value, then it'll be assumed as 'implied' + */ + printf("%s=\"%s\"\n", lcinfo[i].name, vval); + } else { + printf("%s=\"%s\"\n", lcinfo[i].name, vval); + } + } + + vval = getenv("LC_ALL"); + if (vval == NULL) { + vval = ""; + } + printf("LC_ALL=\"%s\"\n", vval); +} + +/* + * keyword value lookup helper (via localeconv()) + */ +char * +kwval_lconv(int id) +{ + struct lconv *lc; + char *rval; + + rval = NULL; + lc = localeconv(); + switch (id) { + case KW_GROUPING: + rval = lc->grouping; + break; + case KW_INT_CURR_SYMBOL: + rval = lc->int_curr_symbol; + break; + case KW_CURRENCY_SYMBOL: + rval = lc->currency_symbol; + break; + case KW_MON_DECIMAL_POINT: + rval = lc->mon_decimal_point; + break; + case KW_MON_THOUSANDS_SEP: + rval = lc->mon_thousands_sep; + break; + case KW_MON_GROUPING: + rval = lc->mon_grouping; + break; + case KW_POSITIVE_SIGN: + rval = lc->positive_sign; + break; + case KW_NEGATIVE_SIGN: + rval = lc->negative_sign; + break; + case KW_INT_FRAC_DIGITS: + rval = &(lc->int_frac_digits); + break; + case KW_FRAC_DIGITS: + rval = &(lc->frac_digits); + break; + case KW_P_CS_PRECEDES: + rval = &(lc->p_cs_precedes); + break; + case KW_P_SEP_BY_SPACE: + rval = &(lc->p_sep_by_space); + break; + case KW_N_CS_PRECEDES: + rval = &(lc->n_cs_precedes); + break; + case KW_N_SEP_BY_SPACE: + rval = &(lc->n_sep_by_space); + break; + case KW_P_SIGN_POSN: + rval = &(lc->p_sign_posn); + break; + case KW_N_SIGN_POSN: + rval = &(lc->n_sign_posn); + break; + case KW_INT_P_CS_PRECEDES: + rval = &(lc->int_p_cs_precedes); + break; + case KW_INT_P_SEP_BY_SPACE: + rval = &(lc->int_p_sep_by_space); + break; + case KW_INT_N_CS_PRECEDES: + rval = &(lc->int_n_cs_precedes); + break; + case KW_INT_N_SEP_BY_SPACE: + rval = &(lc->int_n_sep_by_space); + break; + case KW_INT_P_SIGN_POSN: + rval = &(lc->int_p_sign_posn); + break; + case KW_INT_N_SIGN_POSN: + rval = &(lc->int_n_sign_posn); + break; + default: + break; + } + return (rval); +} + +/* + * keyword value and properties lookup + */ +int +kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr) +{ + int rval; + size_t i; + + rval = 0; + for (i = 0; i < NKWINFO; i++) { + if (strcasecmp(kwname, kwinfo[i].name) == 0) { + rval = 1; + *cat = kwinfo[i].catid; + *isstr = kwinfo[i].isstr; + if (kwinfo[i].value_ref < KW_ZERO) { + *kwval = nl_langinfo(kwinfo[i].value_ref); + } else { + *kwval = kwval_lconv(kwinfo[i].value_ref); + } + break; + } + } + + return (rval); +} + +/* + * Show details about requested keyword according to '-k' and/or '-c' + * command line options specified. + */ +void +showdetails(char *kw) +{ + int isstr, cat, tmpval; + char *kwval; + + if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) { + /* + * invalid keyword specified. + * XXX: any actions? + */ + return; + } + + if (prt_categories) { + printf("%s\n", lookup_localecat(cat)); + } + + if (prt_keywords) { + if (isstr) { + printf("%s=\"%s\"\n", kw, kwval); + } else { + tmpval = (char) *kwval; + printf("%s=%d\n", kw, tmpval); + } + } + + if (!prt_categories && !prt_keywords) { + if (isstr) { + printf("%s\n", kwval); + } else { + tmpval = (char) *kwval; + printf("%d\n", tmpval); + } + } +} + +/* + * Convert locale category id into string + */ +const char * +lookup_localecat(int cat) +{ + size_t i; + + for (i = 0; i < NLCINFO; i++) + if (lcinfo[i].id == cat) { + return (lcinfo[i].name); + } + return ("UNKNOWN"); +} + +/* + * Show list of keywords + */ +void +showkeywordslist(void) +{ + size_t i; + +#define FMT "%-20s %-12s %-7s %-20s\n" + + printf("List of available keywords\n\n"); + printf(FMT, "Keyword", "Category", "Type", "Comment"); + printf("-------------------- ------------ ------- --------------------\n"); + for (i = 0; i < NKWINFO; i++) { + printf(FMT, + kwinfo[i].name, + lookup_localecat(kwinfo[i].catid), + (kwinfo[i].isstr == 0) ? "number" : "string", + kwinfo[i].comment); + } +} diff --git a/usr.bin/logger/logger.1 b/usr.bin/logger/logger.1 new file mode 100644 index 0000000..da47b68 --- /dev/null +++ b/usr.bin/logger/logger.1 @@ -0,0 +1,117 @@ +.\" $NetBSD: logger.1,v 1.15 2012/05/13 17:08:31 njoly Exp $ +.\" +.\" Copyright (c) 1983, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)logger.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd April 26, 2012 +.Dt LOGGER 1 +.Os +.Sh NAME +.Nm logger +.Nd make entries in the system log +.Sh SYNOPSIS +.Nm +.Op Fl cins +.Op Fl d Ar SD +.Op Fl f Ar file +.Op Fl m Ar msgid +.Op Fl p Ar pri +.Op Fl t Ar tag +.Op Ar message ... +.Sh DESCRIPTION +.Nm +provides a shell command interface to the +.Xr syslog 3 +system log module. +.Pp +Options: +.Pp +.Bl -tag -width "messageXX" +.It Fl c +Log to console +.Dv ( LOG_CONS ) . +.It Fl d Ar sd +Log this in the structured data (SD) field. +.Po +.Ar sd +has to be passed as one argument and will require careful quoting when used from +the shell. +.Pc +.It Fl f Ar file +Log the specified file. +.It Fl i +Log the process id of the logger process +with each line +.Dv ( LOG_PID ) . +.It Fl m Ar msgid +The MSGID used for the message. +.It Fl n +Open log file immediately +.Dv ( LOG_NDELAY ) . +.It Fl p Ar pri +Enter the message with the specified priority. +The priority may be specified numerically or as a +.Dq facility.level +pair. +For example, +.Dq \-p local3.info +logs the message(s) as +.Ar info Ns rmational +level in the +.Ar local3 +facility. +The default is +.Dq user.notice . +.It Fl s +Log the message to standard error, as well as the system log +.Dv ( LOG_PERROR ) . +.It Fl t Ar tag +Mark every line in the log with the specified +.Ar tag . +.It Ar message +Write the message to log; if not specified, and the +.Fl f +flag is not provided, standard input is logged. +.El +.Sh EXIT STATUS +.Ex -std logger +.Sh EXAMPLES +.Bd -literal -offset indent -compact +logger System rebooted + +logger \-p local0.notice \-t HOSTIDM \-f /dev/idmc +.Ed +.Sh SEE ALSO +.Xr syslog 3 , +.Xr syslogd 8 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/logger/logger.c b/usr.bin/logger/logger.c new file mode 100644 index 0000000..b87aa5d --- /dev/null +++ b/usr.bin/logger/logger.c @@ -0,0 +1,203 @@ +/* $NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)logger.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: logger.c,v 1.17 2012/04/27 06:30:48 wiz Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +#define SYSLOG_NAMES +#include + +static int decode(const char *, const CODE *); +static int pencode(char *); +__dead static void usage(void); + +/* + * logger -- read and log utility + * + * Reads from an input and arranges to write the result on the system + * log. + */ +int +main(int argc, char *argv[]) +{ + int ch, logflags, pri; + const char *tag; + const char *sd = "-"; + const char *msgid = "-"; + char buf[1024]; + + tag = NULL; + pri = LOG_NOTICE; + logflags = 0; + while ((ch = getopt(argc, argv, "cd:f:im:np:st:")) != -1) + switch((char)ch) { + case 'c': /* log to console */ + logflags |= LOG_CONS; + break; + case 'd': /* structured data field */ + sd = optarg; + break; + case 'f': /* file to log */ + if (freopen(optarg, "r", stdin) == NULL) + err(EXIT_FAILURE, "%s", optarg); + break; + case 'i': /* log process id also */ + logflags |= LOG_PID; + break; + case 'm': /* msgid field */ + msgid = optarg; + break; + case 'n': /* open log file immediately */ + logflags |= LOG_NDELAY; + break; + case 'p': /* priority */ + pri = pencode(optarg); + break; + case 's': /* log to standard error */ + logflags |= LOG_PERROR; + break; + case 't': /* tag */ + tag = optarg; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + /* setup for logging */ + openlog(tag != NULL ? tag : getlogin(), logflags, 0); + (void)fclose(stdout); + + /* log input line if appropriate */ + if (argc > 0) { + char *p, *endp; + size_t len; + + for (p = buf, endp = buf + sizeof(buf) - 2; *argv != NULL;) { + len = strlen(*argv); + if (p + len > endp && p > buf) { + syslogp(pri, msgid, sd, "%s", buf); + p = buf; + } + if (len > sizeof(buf) - 1) + syslogp(pri, msgid, sd, "%s", *argv++); + else { + if (p != buf) + *p++ = ' '; + memmove(p, *argv++, len); + *(p += len) = '\0'; + } + } + if (p != buf) + syslogp(pri, msgid, sd, "%s", buf); + } else /* TODO: allow syslog-protocol messages from file/stdin + * but that will require parsing the line to split + * it into three fields. + */ + while (fgets(buf, sizeof(buf), stdin) != NULL) + syslogp(pri, msgid, sd, "%s", buf); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +/* + * Decode a symbolic name to a numeric value + */ +static int +pencode(char *s) +{ + char *save; + int fac, lev; + + for (save = s; *s != '\0' && *s != '.'; ++s) + ; + if (*s != '\0') { + *s = '\0'; + fac = decode(save, facilitynames); + if (fac < 0) + errx(EXIT_FAILURE, "unknown facility name: %s", save); + *s++ = '.'; + } else { + fac = 0; + s = save; + } + lev = decode(s, prioritynames); + if (lev < 0) + errx(EXIT_FAILURE, "unknown priority name: %s", s); + return ((lev & LOG_PRIMASK) | (fac & LOG_FACMASK)); +} + +static int +decode(const char *name, const CODE *codetab) +{ + const CODE *c; + + if (isdigit((unsigned char)*name)) + return (atoi(name)); + + for (c = codetab; c->c_name != NULL; c++) + if (strcasecmp(name, c->c_name) == 0) + return (c->c_val); + + return (-1); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, + "Usage: %s [-cins] [-d SD] [-f file] [-m msgid] " + "[-p pri] [-t tag] [message ...]\n", + getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/logname/logname.1 b/usr.bin/logname/logname.1 new file mode 100644 index 0000000..242ac00 --- /dev/null +++ b/usr.bin/logname/logname.1 @@ -0,0 +1,72 @@ +.\" $NetBSD: logname.1,v 1.14 2017/07/04 07:02:44 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)logname.1 8.1 (Berkeley) 6/9/93 +.\" +.Dd June 9, 1993 +.Dt LOGNAME 1 +.Os +.Sh NAME +.Nm logname +.Nd display user's login name +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility writes the user's login name to standard output followed by +a newline. +.Pp +The +.Nm +utility explicitly ignores the +.Ev LOGNAME +and +.Ev USER +environment variables +because the environment cannot be trusted. +.Sh EXIT STATUS +.Ex -std logname +.Sh SEE ALSO +.Xr who 1 , +.Xr whoami 1 , +.Xr getlogin 2 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.4 . diff --git a/usr.bin/logname/logname.c b/usr.bin/logname/logname.c new file mode 100644 index 0000000..77f1009 --- /dev/null +++ b/usr.bin/logname/logname.c @@ -0,0 +1,85 @@ +/* $NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $ */ + +/*- + * Copyright (c) 1991, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)logname.c 8.2 (Berkeley) 4/3/94"; +#endif +__RCSID("$NetBSD: logname.c,v 1.10 2011/09/04 20:29:12 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + int ch; + char *p; + + setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + usage(); + /* NOTREACHED */ + } + + if (argc != optind) { + usage(); + /* NOTREACHED */ + } + + if ((p = getlogin()) == NULL) + err(1, "getlogin"); + (void)printf("%s\n", p); + exit(0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: logname\n"); + exit(1); +} diff --git a/usr.bin/m4/NOTES b/usr.bin/m4/NOTES new file mode 100644 index 0000000..d60f80e --- /dev/null +++ b/usr.bin/m4/NOTES @@ -0,0 +1,64 @@ +m4 - macro processor + +PD m4 is based on the macro tool distributed with the software +tools (VOS) package, and described in the "SOFTWARE TOOLS" and +"SOFTWARE TOOLS IN PASCAL" books. It has been expanded to include +most of the command set of SysV m4, the standard UN*X macro processor. + +Since both PD m4 and UN*X m4 are based on SOFTWARE TOOLS macro, +there may be certain implementation similarities between +the two. The PD m4 was produced without ANY references to m4 +sources. + +written by: Ozan S. Yigit + +References: + + Software Tools distribution: macro + + Kernighan, Brian W. and P. J. Plauger, SOFTWARE + TOOLS IN PASCAL, Addison-Wesley, Mass. 1981 + + Kernighan, Brian W. and P. J. Plauger, SOFTWARE + TOOLS, Addison-Wesley, Mass. 1976 + + Kernighan, Brian W. and Dennis M. Ritchie, + THE M4 MACRO PROCESSOR, Unix Programmer's Manual, + Seventh Edition, Vol. 2, Bell Telephone Labs, 1979 + + System V man page for M4 + + +Implementation Notes: + +[1] PD m4 uses a different (and simpler) stack mechanism than the one + described in Software Tools and Software Tools in Pascal books. + The triple stack thing is replaced with a single stack containing + the call frames and the arguments. Each frame is back-linked to a + previous stack frame, which enables us to rewind the stack after + each nested call is completed. Each argument is a character pointer + to the beginning of the argument string within the string space. + The only exceptions to this are (*) arg 0 and arg 1, which are + the macro definition and macro name strings, stored dynamically + for the hash table. + + . . + | . | <-- sp | . | + +-------+ +-----+ + | arg 3 ------------------------------->| str | + +-------+ | . | + | arg 2 --------------+ . + +-------+ | + * | | | + +-------+ | +-----+ + | plev | <-- fp +---------------->| str | + +-------+ | . | + | type | . + +-------+ + | prcf -----------+ plev: paren level + +-------+ | type: call type + | . | | prcf: prev. call frame + . | + +-------+ | + | <----------+ + +-------+ diff --git a/usr.bin/m4/PSD.doc/Makefile b/usr.bin/m4/PSD.doc/Makefile new file mode 100644 index 0000000..9de1f83 --- /dev/null +++ b/usr.bin/m4/PSD.doc/Makefile @@ -0,0 +1,10 @@ +# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:04 dholland Exp $ +# +# @(#)Makefile 8.1 (Berkeley) 6/8/93 + +SECTION=psd +ARTICLE=m4 +SRCS= m4.ms +MACROS= -msU + +.include diff --git a/usr.bin/m4/TEST/ack.m4 b/usr.bin/m4/TEST/ack.m4 new file mode 100644 index 0000000..2784c45 --- /dev/null +++ b/usr.bin/m4/TEST/ack.m4 @@ -0,0 +1,41 @@ +# $NetBSD: ack.m4,v 1.4 1995/09/28 05:37:54 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)ack.m4 8.1 (Berkeley) 6/6/93 +# + +define(ack, `ifelse($1,0,incr($2),$2,0,`ack(DECR($1),1)', +`ack(DECR($1), ack($1,DECR($2)))')') diff --git a/usr.bin/m4/TEST/hanoi.m4 b/usr.bin/m4/TEST/hanoi.m4 new file mode 100644 index 0000000..6f73431 --- /dev/null +++ b/usr.bin/m4/TEST/hanoi.m4 @@ -0,0 +1,46 @@ +# $NetBSD: hanoi.m4,v 1.4 1995/09/28 05:37:56 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)hanoi.m4 8.1 (Berkeley) 6/6/93 +# + +define(hanoi, `trans(A, B, C, $1)') + +define(moved,`move disk from $1 to $2 +') + +define(trans, `ifelse($4,1,`moved($1,$2)', + `trans($1,$3,$2,DECR($4))moved($1,$2)trans($3,$2,$1,DECR($4))')') diff --git a/usr.bin/m4/TEST/hash.m4 b/usr.bin/m4/TEST/hash.m4 new file mode 100644 index 0000000..5de2f79 --- /dev/null +++ b/usr.bin/m4/TEST/hash.m4 @@ -0,0 +1,56 @@ +# $NetBSD: hash.m4,v 1.4 1995/09/28 05:37:58 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)hash.m4 8.1 (Berkeley) 6/6/93 +# + +dnl This probably will not run on any m4 that cannot +dnl handle char constants in eval. +dnl +changequote(<,>) define(HASHVAL,99) dnl +define(hash,) dnl +define(str, + ,1),)>) + >) dnl +define(KEYWORD,<$1,hash($1),>) dnl +define(TSTART, +) dnl +define(TEND,< "",0 +};>) dnl diff --git a/usr.bin/m4/TEST/math.m4 b/usr.bin/m4/TEST/math.m4 new file mode 100644 index 0000000..dff3937 --- /dev/null +++ b/usr.bin/m4/TEST/math.m4 @@ -0,0 +1,182 @@ +dnl $NetBSD: math.m4,v 1.2 2005/10/06 17:38:09 drochner Exp $ +dnl FreeBSD: /repoman/r/ncvs/src/usr.bin/m4/TEST/math.m4,v 1.1 2004/05/01 03:27:05 smkelly Exp +dnl A regression test for m4 C operators (ksb,petef) +dnl If you think you have a short-circuiting m4, run us m4 -DSHORCIRCUIT=yes +dnl +dnl first level of precedence +ifelse(eval(-7),-7,,`failed - +')dnl +ifelse(eval(- -2),2,,`failed - +')dnl +ifelse(eval(!0),1,,`failed ! +')dnl +ifelse(eval(!7),0,,`failed ! +')dnl +ifelse(eval(~-1),0,,`failed ~ +')dnl +dnl next level of precedence +ifelse(eval(3*5),15,,`failed * +')dnl +ifelse(eval(3*0),0,,`failed * +')dnl +ifelse(eval(11/2),5,,`failed / +')dnl +ifelse(eval(1/700),0,,`failed / +')dnl +ifelse(eval(10%5),0,,`failed % +')dnl +ifelse(eval(2%5),2,,`failed % +')dnl +ifelse(eval(2%-1),0,,`failed % +')dnl +dnl next level of precedence +ifelse(eval(2+2),4,,`failed + +')dnl +ifelse(eval(2+-2),0,,`failed + +')dnl +ifelse(eval(2- -2),4,,`failed - +')dnl +ifelse(eval(2-2),0,,`failed - +')dnl +dnl next level of precedence +ifelse(eval(1<<4),16,,`failed << +')dnl +ifelse(eval(16>>4),1,,`failed >> +')dnl +dnl next level of precedence +ifelse(eval(4<4),0,,`failed < +')dnl +ifelse(eval(4<5),1,,`failed < +')dnl +ifelse(eval(4<3),0,,`failed < +')dnl +ifelse(eval(4>4),0,,`failed > +')dnl +ifelse(eval(4>5),0,,`failed > +')dnl +ifelse(eval(4>3),1,,`failed > +')dnl +ifelse(eval(4<=4),1,,`failed <= +')dnl +ifelse(eval(4<=5),1,,`failed <= +')dnl +ifelse(eval(4<=3),0,,`failed <= +')dnl +ifelse(eval(4>=4),1,,`failed >= +')dnl +ifelse(eval(4>=5),0,,`failed >= +')dnl +ifelse(eval(4>=3),1,,`failed >= +')dnl +dnl next level of precedence +ifelse(eval(1==1),1,,`failed == +')dnl +ifelse(eval(1==-1),0,,`failed == +')dnl +ifelse(eval(1!=1),0,,`failed != +')dnl +ifelse(eval(1!=2),1,,`failed != +')dnl +dnl next level of precedence +ifelse(eval(3&5),1,,`failed & +')dnl +ifelse(eval(8&7),0,,`failed & +')dnl +dnl next level of precedence +ifelse(eval(1^1),0,,`failed ^ +')dnl +ifelse(eval(21^5),16,,`failed ^ +')dnl +dnl next level of precedence +ifelse(eval(1|1),1,,`failed | +')dnl +ifelse(eval(21|5),21,,`failed | +')dnl +ifelse(eval(100|1),101,,`failed | +')dnl +dnl next level of precedence +ifelse(eval(1&&1),1,,`failed && +')dnl +ifelse(eval(0&&1),0,,`failed && +')dnl +ifelse(eval(1&&0),0,,`failed && +')dnl +ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0&&10/0),0,,`failed && shortcircuit +')')dnl +dnl next level of precedence +ifelse(eval(1||1),1,,`failed || +')dnl +ifelse(eval(1||0),1,,`failed || +')dnl +ifelse(eval(0||0),0,,`failed || +')dnl +ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1||10/0),1,,`failed || shortcircuit +')')dnl +dnl next level of precedence +ifelse(eval(0 ? 2 : 5),5,,`failed ?: +')dnl +ifelse(eval(1 ? 2 : 5),2,,`failed ?: +')dnl +ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(0 ? 10/0 : 7),7,,`failed ?: shortcircuit +')')dnl +ifelse(SHORTCIRCUIT,`yes',`ifelse(eval(1 ? 7 : 10/0),7,,`failed ?: shortcircuit +')')dnl +dnl operator precedence +ifelse(eval(!0*-2),-2,,`precedence wrong, ! * +')dnl +ifelse(eval(~8/~2),3,,`precedence wrong ~ / +')dnl +ifelse(eval(~-20%7),5,,`precedence wrong ~ % +')dnl +ifelse(eval(3*2+100),106,,`precedence wrong * + +')dnl +ifelse(eval(3+2*100),203,,`precedence wrong + * +')dnl +ifelse(eval(2%5-6/3),0,,`precedence wrong % - +')dnl +ifelse(eval(2/5-5%3),-2,,`precedence wrong / - +')dnl +ifelse(eval(2+5%5+1),3,,`precedence wrong % + +')dnl +ifelse(eval(7+9<<1),32,,`precedence wrong + << +')dnl +ifelse(eval(35-3>>2),8,,`precedence wrong - >> +')dnl +ifelse(eval(9<10<<5),1,,`precedence wrong << < +')dnl +ifelse(eval(9>10<<5),0,,`precedence wrong << > +')dnl +ifelse(eval(32>>2<32),1,,`precedence wrong >> < +')dnl +ifelse(eval(9<=10<<5),1,,`precedence wrong << < +')dnl +ifelse(eval(5<<1<=20>>1),1,,`precedence wrong << <= +')dnl +ifelse(eval(5<<1>=20>>1),1,,`precedence wrong << >= +')dnl +ifelse(eval(0<7==5>=5),1,,`precedence wrong < == +')dnl +ifelse(eval(0<7!=5>=5),0,,`precedence wrong < != +')dnl +ifelse(eval(0>7==5>=5),0,,`precedence wrong > == +')dnl +ifelse(eval(0>7!=5>=5),1,,`precedence wrong > != +')dnl +ifelse(eval(1&7==7),1,,`precedence wrong & == +')dnl +ifelse(eval(0&7!=6),0,,`precedence wrong & != +')dnl +ifelse(eval(9&1|5),5,,`precedence wrong & | +')dnl +ifelse(eval(9&1^5),4,,`precedence wrong & ^ +')dnl +ifelse(eval(9^1|5),13,,`precedence wrong ^ | +')dnl +ifelse(eval(5|0&&1),1,,`precedence wrong | && +')dnl +ifelse(eval(5&&0||0&&5||5),1,,`precedence wrong && || +')dnl +ifelse(eval(0 || 1 ? 0 : 1),0,,`precedence wrong || ?: +')dnl +ifelse(eval(5&&(0||0)&&(5||5)),0,,`precedence wrong || parens +')dnl diff --git a/usr.bin/m4/TEST/sqroot.m4 b/usr.bin/m4/TEST/sqroot.m4 new file mode 100644 index 0000000..6fc8d2c --- /dev/null +++ b/usr.bin/m4/TEST/sqroot.m4 @@ -0,0 +1,46 @@ +# $NetBSD: sqroot.m4,v 1.4 1995/09/28 05:38:01 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)sqroot.m4 8.1 (Berkeley) 6/6/93 +# + +define(square_root, + `ifelse(eval($1<0),1,negative-square-root, + `square_root_aux($1, 1, eval(($1+1)/2))')') +define(square_root_aux, + `ifelse($3, $2, $3, + $3, eval($1/$2), $3, + `square_root_aux($1, $3, eval(($3+($1/$3))/2))')') diff --git a/usr.bin/m4/TEST/string.m4 b/usr.bin/m4/TEST/string.m4 new file mode 100644 index 0000000..a14f14a --- /dev/null +++ b/usr.bin/m4/TEST/string.m4 @@ -0,0 +1,46 @@ +# $NetBSD: string.m4,v 1.4 1995/09/28 05:38:03 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)string.m4 8.1 (Berkeley) 6/6/93 +# + +define(string,`integer $1(len(substr($2,1))) +str($1,substr($2,1),0) +data $1(len(substr($2,1)))/EOS/ +') + +define(str,`ifelse($2,",,data $1(incr($3))/`LET'substr($2,0,1)/ +`str($1,substr($2,1),incr($3))')') diff --git a/usr.bin/m4/TEST/test.m4 b/usr.bin/m4/TEST/test.m4 new file mode 100644 index 0000000..5b19807 --- /dev/null +++ b/usr.bin/m4/TEST/test.m4 @@ -0,0 +1,244 @@ +# $NetBSD: test.m4,v 1.4 1995/09/28 05:38:05 tls Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# This code is derived from software contributed to Berkeley by +# Ozan Yigit. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)test.m4 8.1 (Berkeley) 6/6/93 +# + +# test file for mp (not comprehensive) +# +# v7 m4 does not have `decr'. +# +define(DECR,`eval($1-1)') +# +# include string macros +# +include(string.m4) +# +# create some fortrash strings for an even uglier language +# +string(TEXT, "text") +string(DATA, "data") +string(BEGIN, "begin") +string(END, "end") +string(IF, "if") +string(THEN, "then") +string(ELSE, "else") +string(CASE, "case") +string(REPEAT, "repeat") +string(WHILE, "while") +string(DEFAULT, "default") +string(UNTIL, "until") +string(FUNCTION, "function") +string(PROCEDURE, "procedure") +string(EXTERNAL, "external") +string(FORWARD, "forward") +string(TYPE, "type") +string(VAR, "var") +string(CONST, "const") +string(PROGRAM, "program") +string(INPUT, "input") +string(OUTPUT, "output") +# +divert(2) +diversion #1 +divert(3) +diversion #2 +divert(4) +diversion #3 +divert(5) +diversion #4 +divert(0) +define(abc,xxx) +ifdef(`abc',defined,undefined) +# +# v7 m4 does this wrong. The right output is +# this is A vEry lon sEntEnCE +# see m4 documentation for translit. +# +translit(`this is a very long sentence', abcdefg, ABCDEF) +# +# include towers-of-hanoi +# +include(hanoi.m4) +# +# some reasonable set of disks +# +hanoi(6) +# +# include ackermann's function +# +include(ack.m4) +# +# something like (3,3) will blow away un*x m4. +# +ack(2,3) +# +# include a square_root function for fixed nums +# +include(sqroot.m4) +# +# some square roots. +# +square_root(15) +square_root(100) +square_root(-4) +square_root(21372) +# +# some textual material for enjoyment. +# +[taken from the 'Clemson University Computer Newsletter', + September 1981, pp. 6-7] + +I am a wizard in the magical Kingdom of Transformation and I +slay dragons for a living. Actually, I am a systems programmer. +One of the problems with systems programming is explaining to +non-computer enthusiasts what that is. All of the terms I use to +describe my job are totally meaningless to them. Usually my response +to questions about my work is to say as little as possible. For +instance, if someone asks what happened at work this week, I say +"Nothing much" and then I change the subject. + +With the assistance of my brother, a mechanical engineer, I have devised +an analogy that everyone can understand. The analogy describes the +"Kingdom of Transformation" where travelers wander and are magically +transformed. This kingdom is the computer and the travelers are information. +The purpose of the computer is to change information to a more meaningful +forma. The law of conservation applies here: The computer never creates +and never intentionally destroys data. With no further ado, let us travel +to the Kingdom of Transformation: + +In a land far, far away, there is a magical kingdom called the Kingdom of +Transformation. A king rules over this land and employs a Council of +Wizardry. The main purpose of this kingdom is to provide a way for +neighboring kingdoms to transform citizens into more useful citizens. This +is done by allowing the citizens to enter the kingdom at one of its ports +and to travel any of the many routes in the kingdom. They are magically +transformed along the way. The income of the Kingdom of Transformation +comes from the many toll roads within its boundaries. + +The Kingdom of Transformation was created when several kingdoms got +together and discovered a mutual need for new talents and abilities for +citizens. They employed CTK, Inc. (Creators of Transformation, Inc.) to +create this kingdom. CTK designed the country, its transportation routes, +and its laws of transformation, and created the major highway system. + +Hazards +======= + +Because magic is not truly controllable, CTK invariably, but unknowingly, +creates dragons. Dragons are huge fire-breathing beasts which sometimes +injure or kill travelers. Fortunately, they do not travel, but always +remain near their den. + +Other hazards also exist which are potentially harmful. As the roads +become older and more weatherbeaten, pot-holes will develop, trees will +fall on travelers, etc. CTK maintenance men are called to fix these +problems. + +Wizards +======= + +The wizards play a major role in creating and maintaining the kingdom but +get little credit for their work because it is performed secretly. The +wizards do not wan the workers or travelers to learn their incantations +because many laws would be broken and chaos would result. + +CTK's grand design is always general enough to be applicable in many +different situations. As a result, it is often difficult to use. The +first duty of the wizards is to tailor the transformation laws so as to be +more beneficial and easier to use in their particular environment. + +After creation of the kingdom, a major duty of the wizards is to search for +and kill dragons. If travelers do not return on time or if they return +injured, the ruler of the country contacts the wizards. If the wizards +determine that the injury or death occurred due to the traveler's +negligence, they provide the traveler's country with additional warnings. +If not, they must determine if the cause was a road hazard or a dragon. If +the suspect a road hazard, they call in a CTK maintenance man to locate the +hazard and to eliminate it, as in repairing the pothole in the road. If +they think that cause was a dragon, then they must find and slay it. + +The most difficult part of eliminating a dragon is finding it. Sometimes +the wizard magically knows where the dragon's lair it, but often the wizard +must send another traveler along the same route and watch to see where he +disappears. This sounds like a failsafe method for finding dragons (and a +suicide mission for thr traveler) but the second traveler does not always +disappear. Some dragons eat any traveler who comes too close; others are +very picky. + +The wizards may call in CTK who designed the highway system and +transformation laws to help devise a way to locate the dragon. CTK also +helps provide the right spell or incantation to slay the dragon. (There is +no general spell to slay dragons; each dragon must be eliminated with a +different spell.) + +Because neither CTK nor wizards are perfect, spells to not always work +correctly. At best, nothing happens when the wrong spell is uttered. At +worst, the dragon becomes a much larger dragon or multiplies into several +smaller ones. In either case, new spells must be found. + +If all existing dragons are quiet (i.e. have eaten sufficiently), wizards +have time to do other things. They hide in castles and practice spells and +incatations. They also devise shortcuts for travelers and new laws of +transformation. + +Changes in the Kingdom +====================== + +As new transformation kingdoms are created and old ones are maintained, +CTK, Inc. is constantly learning new things. It learns ways to avoid +creating some of the dragons that they have previously created. It also +discovers new and better laws of transformation. As a result, CTK will +periodically create a new grand design which is far better than the old. +The wizards determine when is a good time to implement this new design. +This is when the tourist season is slow or when no important travelers +(VIPs) are to arrive. The kingdom must be closed for the actual +implementation and is leter reopened as a new and better place to go. + +A final question you might ask is what happens when the number of tourists +becomes too great for the kingdom to handle in a reasonable period of time +(i.e., the tourist lines at the ports are too long). The Kingdom of +Transformation has three options: (1) shorten the paths that a tourist must +travel, or (2) convince CTK to develop a faster breed of horses so that the +travelers can finish sooner, or (3) annex more territories so that the +kingdom can handle more travelers. + +Thus ends the story of the Kingdom of Transformation. I hope this has +explained my job to you: I slay dragons for a living. + +# +#should do an automatic undivert.. +# diff --git a/usr.bin/m4/eval.c b/usr.bin/m4/eval.c new file mode 100644 index 0000000..94150b1 --- /dev/null +++ b/usr.bin/m4/eval.c @@ -0,0 +1,1053 @@ +/* $OpenBSD: eval.c,v 1.66 2008/08/21 21:01:47 espie Exp $ */ +/* $NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * eval.c + * Facility: m4 macro processor + * by: oz + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: eval.c,v 1.27 2018/07/30 22:58:09 kre Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" +#include "pathnames.h" + +static void dodefn(const char *); +static void dopushdef(const char *, const char *); +static void dodump(const char *[], int); +static void dotrace(const char *[], int, int); +static void doifelse(const char *[], int); +static int doincl(const char *); +static int dopaste(const char *); +static void dochq(const char *[], int); +static void dochc(const char *[], int); +static void dom4wrap(const char *); +static void dodiv(int); +static void doundiv(const char *[], int); +static void dosub(const char *[], int); +static void map(char *, const char *, const char *, const char *); +static const char *handledash(char *, char *, const char *); +static void expand_builtin(const char *[], int, int); +static void expand_macro(const char *[], int); +static void dump_one_def(const char *, struct macro_definition *); + +unsigned long expansion_id; + +/* + * eval - eval all macros and builtins calls + * argc - number of elements in argv. + * argv - element vector : + * argv[0] = definition of a user + * macro or NULL if built-in. + * argv[1] = name of the macro or + * built-in. + * argv[2] = parameters to user-defined + * . macro or built-in. + * . + * + * A call in the form of macro-or-builtin() will result in: + * argv[0] = nullstr + * argv[1] = macro-or-builtin + * argv[2] = nullstr + * + * argc is 3 for macro-or-builtin() and 2 for macro-or-builtin + */ +void +eval(const char *argv[], int argc, int td, int is_traced) +{ + size_t mark = SIZE_MAX; + + expansion_id++; + if (td & RECDEF) + m4errx(1, "expanding recursive definition for %s.", argv[1]); + if (is_traced) + mark = trace(argv, argc, infile+ilevel); + if (td == MACRTYPE) + expand_macro(argv, argc); + else + expand_builtin(argv, argc, td); + if (mark != SIZE_MAX) + finish_trace(mark); +} + +/* + * expand_builtin - evaluate built-in macros. + */ +void +expand_builtin(const char *argv[], int argc, int td) +{ + int c, n; + int ac; + static int sysval = 0; + +#ifdef DEBUG + printf("argc = %d\n", argc); + for (n = 0; n < argc; n++) + printf("argv[%d] = %s\n", n, argv[n]); + fflush(stdout); +#endif + + /* + * if argc == 3 and argv[2] is null, then we + * have macro-or-builtin() type call. We adjust + * argc to avoid further checking.. + */ + /* we keep the initial value for those built-ins that differentiate + * between builtin() and builtin. + */ + ac = argc; + + if (argc == 3 && !*(argv[2]) && !mimic_gnu) + argc--; + + switch (td & TYPEMASK) { + + case DEFITYPE: + if (argc > 2) + dodefine(argv[2], (argc > 3) ? argv[3] : null); + break; + + case PUSDTYPE: + if (argc > 2) + dopushdef(argv[2], (argc > 3) ? argv[3] : null); + break; + + case DUMPTYPE: + dodump(argv, argc); + break; + + case TRACEONTYPE: + dotrace(argv, argc, 1); + break; + + case TRACEOFFTYPE: + dotrace(argv, argc, 0); + break; + + case EXPRTYPE: + /* + * doexpr - evaluate arithmetic + * expression + */ + { + int base = 10; + int maxdigits = 0; + int e; + + if (argc > 3) { + base = strtoi(argv[3], NULL, 0, 2, 36, &e); + if (e) { + m4errx(1, "expr: base %s invalid.", argv[3]); + } + } + if (argc > 4) { + maxdigits = strtoi(argv[4], NULL, 0, 0, INT_MAX, &e); + if (e) { + m4errx(1, "expr: maxdigits %s invalid.", argv[4]); + } + } + if (argc > 2) + pbnumbase(expr(argv[2]), base, maxdigits); + break; + } + + case IFELTYPE: + if (argc > 4) + doifelse(argv, argc); + break; + + case IFDFTYPE: + /* + * doifdef - select one of two + * alternatives based on the existence of + * another definition + */ + if (argc > 3) { + if (lookup_macro_definition(argv[2]) != NULL) + pbstr(argv[3]); + else if (argc > 4) + pbstr(argv[4]); + } + break; + + case LENGTYPE: + /* + * dolen - find the length of the + * argument + */ + pbnum((argc > 2) ? strlen(argv[2]) : 0); + break; + + case INCRTYPE: + /* + * doincr - increment the value of the + * argument + */ + if (argc > 2) + pbnum(atoi(argv[2]) + 1); + break; + + case DECRTYPE: + /* + * dodecr - decrement the value of the + * argument + */ + if (argc > 2) + pbnum(atoi(argv[2]) - 1); + break; + + case SYSCTYPE: + /* + * dosys - execute system command + */ + if (argc > 2) { + fflush(stdout); + sysval = system(argv[2]); + } + break; + + case SYSVTYPE: + /* + * dosysval - return value of the last + * system call. + * + */ + pbnum(sysval); + break; + + case ESYSCMDTYPE: + if (argc > 2) + doesyscmd(argv[2]); + break; + case INCLTYPE: + if (argc > 2) + if (!doincl(argv[2])) + err(1, "%s at line %lu: include(%s)", + CURRENT_NAME, CURRENT_LINE, argv[2]); + break; + + case SINCTYPE: + if (argc > 2) + (void) doincl(argv[2]); + break; +#ifdef EXTENDED + case PASTTYPE: + if (argc > 2) + if (!dopaste(argv[2])) + err(1, "%s at line %lu: paste(%s)", + CURRENT_NAME, CURRENT_LINE, argv[2]); + break; + + case SPASTYPE: + if (argc > 2) + (void) dopaste(argv[2]); + break; + case FORMATTYPE: + doformat(argv, argc); + break; +#endif + case CHNQTYPE: + dochq(argv, ac); + break; + + case CHNCTYPE: + dochc(argv, argc); + break; + + case SUBSTYPE: + /* + * dosub - select substring + * + */ + if (argc > 3) + dosub(argv, argc); + break; + + case SHIFTYPE: + /* + * doshift - push back all arguments + * except the first one (i.e. skip + * argv[2]) + */ + if (argc > 3) { + for (n = argc - 1; n > 3; n--) { + pbstr(rquote); + pbstr(argv[n]); + pbstr(lquote); + pushback(COMMA); + } + pbstr(rquote); + pbstr(argv[3]); + pbstr(lquote); + } + break; + + case DIVRTYPE: + if (argc > 2 && (n = atoi(argv[2])) != 0) + dodiv(n); + else { + active = stdout; + oindex = 0; + } + break; + + case UNDVTYPE: + doundiv(argv, argc); + break; + + case DIVNTYPE: + /* + * dodivnum - return the number of + * current output diversion + */ + pbnum(oindex); + break; + + case UNDFTYPE: + /* + * doundefine - undefine a previously + * defined macro(s) or m4 keyword(s). + */ + if (argc > 2) + for (n = 2; n < argc; n++) + macro_undefine(argv[n]); + break; + + case POPDTYPE: + /* + * dopopdef - remove the topmost + * definitions of macro(s) or m4 + * keyword(s). + */ + if (argc > 2) + for (n = 2; n < argc; n++) + macro_popdef(argv[n]); + break; + + case MKTMTYPE: + /* + * dotemp - create a temporary file + */ + if (argc > 2) { + int fd; + char *temp; + + temp = xstrdup(argv[2]); + + fd = mkstemp(temp); + if (fd == -1) + err(1, + "%s at line %lu: couldn't make temp file %s", + CURRENT_NAME, CURRENT_LINE, argv[2]); + close(fd); + pbstr(temp); + free(temp); + } + break; + + case TRNLTYPE: + /* + * dotranslit - replace all characters in + * the source string that appears in the + * "from" string with the corresponding + * characters in the "to" string. + */ + if (argc > 3) { + char *temp; + + temp = xalloc(strlen(argv[2])+1, NULL); + if (argc > 4) + map(temp, argv[2], argv[3], argv[4]); + else + map(temp, argv[2], argv[3], null); + pbstr(temp); + free(temp); + } else if (argc > 2) + pbstr(argv[2]); + break; + + case INDXTYPE: + /* + * doindex - find the index of the second + * argument string in the first argument + * string. -1 if not present. + */ + pbnum((argc > 3) ? indx(argv[2], argv[3]) : -1); + break; + + case ERRPTYPE: + /* + * doerrp - print the arguments to stderr + * file + */ + if (argc > 2) { + for (n = 2; n < argc; n++) + fprintf(stderr, "%s%s", + mimic_gnu && n == 2 ? "" : " ", + argv[n]); + if (!mimic_gnu) + fprintf(stderr, "\n"); + } + break; + + case DNLNTYPE: + /* + * dodnl - eat-up-to and including + * newline + */ + while ((c = gpbc()) != '\n' && c != EOF) + ; + break; + + case M4WRTYPE: + /* + * dom4wrap - set up for + * wrap-up/wind-down activity + */ + if (argc > 2) + dom4wrap(argv[2]); + break; + + case EXITTYPE: + /* + * doexit - immediate exit from m4. + */ + killdiv(); + exit((argc > 2) ? atoi(argv[2]) : 0); + break; + + case DEFNTYPE: + if (argc > 2) + for (n = 2; n < argc; n++) + dodefn(argv[n]); + break; + + case INDIRTYPE: /* Indirect call */ + if (argc > 2) + doindir(argv, argc); + break; + + case BUILTINTYPE: /* Builtins only */ + if (argc > 2) + dobuiltin(argv, argc); + break; + + case PATSTYPE: + if (argc > 2) + dopatsubst(argv, argc); + break; + case REGEXPTYPE: + if (argc > 2) + doregexp(argv, argc); + break; + case LINETYPE: + doprintlineno(infile+ilevel); + break; + case FILENAMETYPE: + doprintfilename(infile+ilevel); + break; + case SELFTYPE: + pbstr(rquote); + pbstr(argv[1]); + pbstr(lquote); + break; + default: + m4errx(1, "eval: major botch."); + break; + } +} + +/* + * expand_macro - user-defined macro expansion + */ +void +expand_macro(const char *argv[], int argc) +{ + const char *t; + const char *p; + int n; + int argno; + + t = argv[0]; /* defn string as a whole */ + p = t; + while (*p) + p++; + p--; /* last character of defn */ + while (p > t) { + if (*(p - 1) != ARGFLAG) + PUSHBACK(*p); + else { + switch (*p) { + + case '#': + pbnum(argc - 2); + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + argno = *p - '0'; + if (mimic_gnu) { + const unsigned char *q = + (const unsigned char *)p; + while (isdigit(*++q)) { + bp--; + argno = argno * 10 + *q - '0'; + } + } + if (argno < argc - 1) + pbstr(argv[argno + 1]); + break; + case '*': + if (argc > 2) { + for (n = argc - 1; n > 2; n--) { + pbstr(argv[n]); + pushback(COMMA); + } + pbstr(argv[2]); + } + break; + case '@': + if (argc > 2) { + for (n = argc - 1; n > 2; n--) { + pbstr(rquote); + pbstr(argv[n]); + pbstr(lquote); + pushback(COMMA); + } + pbstr(rquote); + pbstr(argv[2]); + pbstr(lquote); + } + break; + default: + PUSHBACK(*p); + PUSHBACK('$'); + break; + } + p--; + } + p--; + } + if (p == t) /* do last character */ + PUSHBACK(*p); +} + + +/* + * dodefine - install definition in the table + */ +void +dodefine(const char *name, const char *defn) +{ + if (!*name && !mimic_gnu) + m4errx(1, "null definition."); + else + macro_define(name, defn); +} + +/* + * dodefn - push back a quoted definition of + * the given name. + */ +static void +dodefn(const char *name) +{ + struct macro_definition *p; + + if ((p = lookup_macro_definition(name)) != NULL) { + if ((p->type & TYPEMASK) == MACRTYPE) { + pbstr(rquote); + pbstr(p->defn); + pbstr(lquote); + } else { + pbstr(p->defn); + pbstr(BUILTIN_MARKER); + } + } +} + +/* + * dopushdef - install a definition in the hash table + * without removing a previous definition. Since + * each new entry is entered in *front* of the + * hash bucket, it hides a previous definition from + * lookup. + */ +static void +dopushdef(const char *name, const char *defn) +{ + if (!*name && !mimic_gnu) + m4errx(1, "null definition."); + else + macro_pushdef(name, defn); +} + +/* + * dump_one_def - dump the specified definition. + */ +static void +dump_one_def(const char *name, struct macro_definition *p) +{ + if (!traceout) + traceout = stderr; + if (mimic_gnu) { + if ((p->type & TYPEMASK) == MACRTYPE) + fprintf(traceout, "%s:\t%s\n", name, p->defn); + else { + fprintf(traceout, "%s:\t<%s>\n", name, p->defn); + } + } else + fprintf(traceout, "`%s'\t`%s'\n", name, p->defn); +} + +/* + * dodumpdef - dump the specified definitions in the hash + * table to stderr. If nothing is specified, the entire + * hash table is dumped. + */ +static void +dodump(const char *argv[], int argc) +{ + int n; + struct macro_definition *p; + + if (argc > 2) { + for (n = 2; n < argc; n++) + if ((p = lookup_macro_definition(argv[n])) != NULL) + dump_one_def(argv[n], p); + } else + macro_for_all(dump_one_def); +} + +/* + * dotrace - mark some macros as traced/untraced depending upon on. + */ +static void +dotrace(const char *argv[], int argc, int on) +{ + int n; + + if (argc > 2) { + for (n = 2; n < argc; n++) + mark_traced(argv[n], on); + } else + mark_traced(NULL, on); +} + +/* + * doifelse - select one of two alternatives - loop. + */ +static void +doifelse(const char *argv[], int argc) +{ + cycle { + if (argc < 5) + m4errx(1, "wrong number of args for ifelse"); + if (STREQ(argv[2], argv[3])) + pbstr(argv[4]); + else if (argc == 6) + pbstr(argv[5]); + else if (argc > 6) { + argv += 3; + argc -= 3; + continue; + } + break; + } +} + +/* + * doinclude - include a given file. + */ +static int +doincl(const char *ifile) +{ +#ifndef REAL_FREEZE + if (thawing) + return 1; +#endif + if (ilevel + 1 == MAXINP) + m4errx(1, "too many include files."); + if (fopen_trypath(infile+ilevel+1, ifile) != NULL) { + ilevel++; + bbase[ilevel] = bufbase = bp; + return (1); + } else + return (0); +} + +#ifdef EXTENDED +/* + * dopaste - include a given file without any + * macro processing. + */ +static int +dopaste(const char *pfile) +{ + FILE *pf; + int c; + + if ((pf = fopen(pfile, "r")) != NULL) { + if (synch_lines) + fprintf(active, "#line 1 \"%s\"\n", pfile); + while ((c = getc(pf)) != EOF) + putc(c, active); + (void) fclose(pf); + emit_synchline(); + return (1); + } else + return (0); +} +#endif + +/* + * dochq - change quote characters + */ +static void +dochq(const char *argv[], int ac) +{ + if (ac == 2) { + lquote[0] = LQUOTE; lquote[1] = EOS; + rquote[0] = RQUOTE; rquote[1] = EOS; + } else { + strlcpy(lquote, argv[2], sizeof(lquote)); + if (ac > 3) { + strlcpy(rquote, argv[3], sizeof(rquote)); + } else { + rquote[0] = ECOMMT; rquote[1] = EOS; + } + } +} + +/* + * dochc - change comment characters + */ +static void +dochc(const char *argv[], int argc) +{ +/* XXX Note that there is no difference between no argument and a single + * empty argument. + */ + if (argc == 2) { + scommt[0] = EOS; + ecommt[0] = EOS; + } else { + strlcpy(scommt, argv[2], sizeof(scommt)); + if (argc == 3) { + ecommt[0] = ECOMMT; ecommt[1] = EOS; + } else { + strlcpy(ecommt, argv[3], sizeof(ecommt)); + } + } +} + +/* + * dom4wrap - expand text at EOF + */ +static void +dom4wrap(const char *text) +{ + if (wrapindex >= maxwraps) { + if (maxwraps == 0) + maxwraps = 16; + else + maxwraps *= 2; + m4wraps = xrealloc(m4wraps, maxwraps * sizeof(*m4wraps), + "too many m4wraps"); + } + m4wraps[wrapindex++] = xstrdup(text); +} + +/* + * dodivert - divert the output to a temporary file + */ +static void +dodiv(int n) +{ + int fd; + + oindex = n; + if (n >= maxout) { + if (mimic_gnu) + resizedivs(n + 10); + else + n = 0; /* bitbucket */ + } + + if (n < 0) + n = 0; /* bitbucket */ + if (outfile[n] == NULL) { + char fname[] = _PATH_DIVNAME; + + if ((fd = mkstemp(fname)) < 0 || + (outfile[n] = fdopen(fd, "w+")) == NULL) + err(1, "%s: cannot divert", fname); + if (unlink(fname) == -1) + err(1, "%s: cannot unlink", fname); + } + active = outfile[n]; +} + +/* + * doundivert - undivert a specified output, or all + * other outputs, in numerical order. + */ +static void +doundiv(const char *argv[], int argc) +{ + int ind; + int n; + + if (argc > 2) { + for (ind = 2; ind < argc; ind++) { + int e; + n = strtoi(argv[ind], NULL, 0, 1, INT_MAX, &e); + if (e) { + if (errno == EINVAL && mimic_gnu) + getdivfile(argv[ind]); + } else { + if (n < maxout && outfile[n] != NULL) + getdiv(n); + } + } + } + else + for (n = 1; n < maxout; n++) + if (outfile[n] != NULL) + getdiv(n); +} + +/* + * dosub - select substring + */ +static void +dosub(const char *argv[], int argc) +{ + const char *ap, *fc, *k; + int nc; + + ap = argv[2]; /* target string */ +#ifdef EXPR + fc = ap + expr(argv[3]); /* first char */ +#else + fc = ap + atoi(argv[3]); /* first char */ +#endif + nc = strlen(fc); + if (argc >= 5) +#ifdef EXPR + nc = min(nc, expr(argv[4])); +#else + nc = min(nc, atoi(argv[4])); +#endif + if (fc >= ap && fc < ap + strlen(ap)) + for (k = fc + nc - 1; k >= fc; k--) + pushback(*k); +} + +/* + * map: + * map every character of s1 that is specified in from + * into s3 and replace in s. (source s1 remains untouched) + * + * This is a standard implementation of map(s,from,to) function of ICON + * language. Within mapvec, we replace every character of "from" with + * the corresponding character in "to". If "to" is shorter than "from", + * than the corresponding entries are null, which means that those + * characters dissapear altogether. Furthermore, imagine + * map(dest, "sourcestring", "srtin", "rn..*") type call. In this case, + * `s' maps to `r', `r' maps to `n' and `n' maps to `*'. Thus, `s' + * ultimately maps to `*'. In order to achieve this effect in an efficient + * manner (i.e. without multiple passes over the destination string), we + * loop over mapvec, starting with the initial source character. if the + * character value (dch) in this location is different than the source + * character (sch), sch becomes dch, once again to index into mapvec, until + * the character value stabilizes (i.e. sch = dch, in other words + * mapvec[n] == n). Even if the entry in the mapvec is null for an ordinary + * character, it will stabilize, since mapvec[0] == 0 at all times. At the + * end, we restore mapvec* back to normal where mapvec[n] == n for + * 0 <= n <= 127. This strategy, along with the restoration of mapvec, is + * about 5 times faster than any algorithm that makes multiple passes over + * destination string. + */ +static void +map(char *dest, const char *src, const char *from, const char *to) +{ + const char *tmp; + unsigned char sch, dch; + unsigned char found[256]; + static char frombis[257]; + static char tobis[257]; + static unsigned char mapvec[256] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, + 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, + 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, + 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, + 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, + 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, + 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, + 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, + 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, + 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, + 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, + 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, + 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, + 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, + 246, 247, 248, 249, 250, 251, 252, 253, 254, 255 + }; + + if (*src) { + if (mimic_gnu) { + /* + * expand character ranges on the fly + */ + from = handledash(frombis, frombis + 256, from); + to = handledash(tobis, tobis + 256, to); + } + tmp = from; + /* + * create a mapping between "from" and + * "to" + */ + memset(found, 0, sizeof(found)); + for (; (sch = (unsigned char)*from) != '\0'; from++) { + if (!mimic_gnu || !found[sch]) { + found[sch] = 1; + mapvec[sch] = *to; + } + if (*to) + to++; + } + + if (mimic_gnu) { + for (; (sch = (unsigned char)*src) != '\0'; src++) { + if (!found[sch]) + *dest++ = sch; + else if ((dch = mapvec[sch]) != '\0') + *dest++ = dch; + } + } else { + while (*src) { + sch = (unsigned char)(*src++); + dch = mapvec[sch]; + while (dch != sch) { + sch = dch; + dch = mapvec[sch]; + } + if ((*dest = (char)dch)) + dest++; + } + } + /* + * restore all the changed characters + */ + while (*tmp) { + mapvec[(unsigned char)(*tmp)] = (unsigned char)(*tmp); + tmp++; + } + } + *dest = '\0'; +} + + +/* + * handledash: + * use buffer to copy the src string, expanding character ranges + * on the way. + */ +static const char * +handledash(char *buffer, char *end, const char *src) +{ + char *p; + + p = buffer; + while(*src) { + if (src[1] == '-' && src[2]) { + unsigned char i; + if ((unsigned char)src[0] <= (unsigned char)src[2]) { + for (i = (unsigned char)src[0]; + i <= (unsigned char)src[2]; i++) { + *p++ = i; + if (p == end) { + *p = '\0'; + return buffer; + } + } + } else { + for (i = (unsigned char)src[0]; + i >= (unsigned char)src[2]; i--) { + *p++ = i; + if (p == end) { + *p = '\0'; + return buffer; + } + } + } + src += 3; + } else + *p++ = *src++; + if (p == end) + break; + } + *p = '\0'; + return buffer; +} diff --git a/usr.bin/m4/expr.c b/usr.bin/m4/expr.c new file mode 100644 index 0000000..a6c901f --- /dev/null +++ b/usr.bin/m4/expr.c @@ -0,0 +1,50 @@ +/* $NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $ */ +/* $OpenBSD: expr.c,v 1.17 2006/01/20 23:10:19 espie Exp $ */ +/* + * Copyright (c) 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: expr.c,v 1.19 2009/10/26 21:11:28 christos Exp $"); +#include +#include +#include +#include "mdef.h" +#include "extern.h" + +int32_t end_result; +const char *copy_toeval; + +extern void yy_scan_string(const char *); +extern int yyparse(void); +extern int yyerror(const char *); + +int +yyerror(const char *msg) +{ + fprintf(stderr, "m4: %s in expr %s\n", msg, copy_toeval); + return(0); +} + +int +expr(const char *toeval) +{ + copy_toeval = toeval; + yy_scan_string(toeval); + yyparse(); + return end_result; +} diff --git a/usr.bin/m4/extern.h b/usr.bin/m4/extern.h new file mode 100644 index 0000000..1cb9de1 --- /dev/null +++ b/usr.bin/m4/extern.h @@ -0,0 +1,190 @@ +/* $OpenBSD: extern.h,v 1.49 2009/10/14 17:19:47 sthen Exp $ */ +/* $NetBSD: extern.h,v 1.19 2016/01/16 18:30:57 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +/* eval.c */ +extern void eval(const char *[], int, int, int); +extern void dodefine(const char *, const char *); +extern unsigned long expansion_id; + +/* expr.c */ +extern int expr(const char *); + +/* gnum4.c */ +extern void addtoincludepath(const char *); +extern struct input_file *fopen_trypath(struct input_file *, const char *); +extern void doindir(const char *[], int); +extern void dobuiltin(const char *[], int); +extern void dopatsubst(const char *[], int); +extern void doregexp(const char *[], int); + +extern void doprintlineno(struct input_file *); +extern void doprintfilename(struct input_file *); + +extern void doesyscmd(const char *); +extern void getdivfile(const char *); +extern void doformat(const char *[], int); +#ifdef REAL_FREEZE +extern void freeze_state(const char *); +extern void thaw_state(const char *); +#endif + + +/* look.c */ + +#define FLAG_UNTRACED 0 +#define FLAG_TRACED 1 +#define FLAG_NO_TRACE 2 + +extern void init_macros(void); +extern ndptr lookup(const char *); +extern void mark_traced(const char *, int); +extern struct ohash macros; + +extern struct macro_definition *lookup_macro_definition(const char *); +extern void macro_define(const char *, const char *); +extern void macro_pushdef(const char *, const char *); +extern void macro_popdef(const char *); +extern void macro_undefine(const char *); +extern void setup_builtin(const char *, unsigned int); +extern void macro_for_all(void (*)(const char *, struct macro_definition *)); +#define macro_getdef(p) ((p)->d) +#define macro_name(p) ((p)->name) +#define macro_builtin_type(p) ((p)->builtin_type) +#define is_traced(p) ((p)->trace_flags == FLAG_NO_TRACE ? (trace_flags & TRACE_ALL) : (p)->trace_flags) + +extern ndptr macro_getbuiltin(const char *); +#ifdef REAL_FREEZE +extern void dump_state(FILE *); +extern void restore_state(FILE *); +#endif + +/* main.c */ +extern void outputstr(const char *); +extern void do_emit_synchline(void); +#define emit_synchline() do { if (synch_lines) do_emit_synchline(); } while(0) + +/* misc.c */ +extern void chrsave(int); +extern char *compute_prevep(void); +extern void getdiv(int); +extern ptrdiff_t indx(const char *, const char *); +extern void initspaces(void); +extern void killdiv(void); +extern void pbnum(int); +extern void pbnumbase(int, int, int); +extern void pbunsigned(unsigned long); +extern void pbstr(const char *); +extern void pushback(int); +extern void *xalloc(size_t, const char *fmt, ...) __printflike(2, 3); +extern void *xrealloc(void *, size_t, const char *fmt, ...) + __printflike(3, 4); +extern char *xstrdup(const char *); +extern void resizedivs(int); +extern size_t buffer_mark(void); +extern void dump_buffer(FILE *, size_t); +extern void __dead m4errx(int, const char *, ...) __printflike(2, 3); + +extern int obtain_char(struct input_file *); +extern void set_input(struct input_file *, FILE *, const char *); +extern void release_input(struct input_file *); + +/* speeded-up versions of chrsave/pushback */ +#define PUSHBACK(c) \ + do { \ + if (bp >= endpbb) \ + enlarge_bufspace(); \ + *bp++ = (c); \ + } while(0) + +#define CHRSAVE(c) \ + do { \ + if (ep >= endest) \ + enlarge_strspace(); \ + *ep++ = (c); \ + } while(0) + +/* and corresponding exposure for local symbols */ +extern void enlarge_bufspace(void); +extern void enlarge_strspace(void); +extern unsigned char *endpbb; +extern char *endest; + +/* trace.c */ +extern unsigned int trace_flags; +#define TRACE_ALL 512 +extern void trace_file(const char *); +extern size_t trace(const char **, int, struct input_file *); +extern void finish_trace(size_t); +extern void set_trace_flags(const char *); +extern FILE *traceout; + +extern ndptr hashtab[]; /* hash table for macros etc. */ +extern stae *mstack; /* stack of m4 machine */ +extern char *sstack; /* shadow stack, for string space extension */ +extern FILE *active; /* active output file pointer */ +extern struct input_file infile[];/* input file stack (0=stdin) */ +extern FILE **outfile; /* diversion array(0=bitbucket) */ +extern int maxout; /* maximum number of diversions */ +extern int fp; /* m4 call frame pointer */ +extern int ilevel; /* input file stack pointer */ +extern int oindex; /* diversion index. */ +extern int sp; /* current m4 stack pointer */ +extern unsigned char *bp; /* first available character */ +extern unsigned char *buf; /* push-back buffer */ +extern unsigned char *bufbase; /* buffer base for this ilevel */ +extern unsigned char *bbase[]; /* buffer base per ilevel */ +extern char ecommt[MAXCCHARS+1];/* end character for comment */ +extern char *ep; /* first free char in strspace */ +extern char lquote[MAXCCHARS+1];/* left quote character (`) */ +extern char **m4wraps; /* m4wrap string default. */ +extern int maxwraps; /* size of m4wraps array */ +extern int wrapindex; /* current index in m4wraps */ +extern int fatal_warnings; /* exit on warning */ +extern int quiet; /* no warnings */ +extern int nesting_limit; /* macro expansion nesting limit */ +#ifndef REAL_FREEZE +extern FILE *freezef; /* copy of input */ +extern int thawing; /* don't process includes during thaw */ +#endif + +extern const char *null; /* as it says.. just a null. */ +extern char rquote[MAXCCHARS+1];/* right quote character (') */ +extern char scommt[MAXCCHARS+1];/* start character for comment */ +extern int synch_lines; /* line synchronisation directives */ + +extern int mimic_gnu; /* behaves like gnu-m4 */ +extern int prefix_builtins; /* prefix builtin macros with m4_ */ diff --git a/usr.bin/m4/gnum4.c b/usr.bin/m4/gnum4.c new file mode 100644 index 0000000..a450a99 --- /dev/null +++ b/usr.bin/m4/gnum4.c @@ -0,0 +1,817 @@ +/* $NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $ */ +/* $OpenBSD: gnum4.c,v 1.39 2008/08/21 21:01:04 espie Exp $ */ + +/* + * Copyright (c) 1999 Marc Espie + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * functions needed to support gnu-m4 extensions, including a fake freezing + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: gnum4.c,v 1.10 2016/01/16 16:59:18 christos Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" + + +int mimic_gnu = 0; +#ifndef SIZE_T_MAX +#define SIZE_T_MAX (size_t)~0ull +#endif + +/* + * Support for include path search + * First search in the current directory. + * If not found, and the path is not absolute, include path kicks in. + * First, -I options, in the order found on the command line. + * Then M4PATH env variable + */ + +struct path_entry { + char *name; + struct path_entry *next; +} *first, *last; + +static struct path_entry *new_path_entry(const char *); +static void ensure_m4path(void); +static struct input_file *dopath(struct input_file *, const char *); + +static struct path_entry * +new_path_entry(const char *dirname) +{ + struct path_entry *n; + + n = malloc(sizeof(struct path_entry)); + if (!n) + errx(1, "out of memory"); + n->name = strdup(dirname); + if (!n->name) + errx(1, "out of memory"); + n->next = 0; + return n; +} + +void +addtoincludepath(const char *dirname) +{ + struct path_entry *n; + + n = new_path_entry(dirname); + + if (last) { + last->next = n; + last = n; + } + else + last = first = n; +} + +static void +ensure_m4path(void) +{ + static int envpathdone = 0; + char *envpath; + char *sweep; + char *path; + + if (envpathdone) + return; + envpathdone = TRUE; + envpath = getenv("M4PATH"); + if (!envpath) + return; + /* for portability: getenv result is read-only */ + envpath = strdup(envpath); + if (!envpath) + errx(1, "out of memory"); + for (sweep = envpath; + (path = strsep(&sweep, ":")) != NULL;) + addtoincludepath(path); + free(envpath); +} + +static +struct input_file * +dopath(struct input_file *i, const char *filename) +{ + char path[MAXPATHLEN]; + struct path_entry *pe; + FILE *f; + + for (pe = first; pe; pe = pe->next) { + snprintf(path, sizeof(path), "%s/%s", pe->name, filename); + if ((f = fopen(path, "r")) != 0) { + set_input(i, f, path); + return i; + } + } + return NULL; +} + +struct input_file * +fopen_trypath(struct input_file *i, const char *filename) +{ + FILE *f; + + f = fopen(filename, "r"); + if (f != NULL) { + set_input(i, f, filename); + return i; + } + if (filename[0] == '/') + return NULL; + + ensure_m4path(); + + return dopath(i, filename); +} + +void +doindir(const char *argv[], int argc) +{ + ndptr n; + struct macro_definition *p; + + n = lookup(argv[2]); + if (n == NULL || (p = macro_getdef(n)) == NULL) + m4errx(1, "indir: undefined macro %s.", argv[2]); + argv[1] = p->defn; + + eval(argv+1, argc-1, p->type, is_traced(n)); +} + +void +dobuiltin(const char *argv[], int argc) +{ + ndptr p; + + argv[1] = NULL; + p = macro_getbuiltin(argv[2]); + if (p != NULL) + eval(argv+1, argc-1, macro_builtin_type(p), is_traced(p)); + else + m4errx(1, "unknown builtin %s.", argv[2]); +} + + +/* We need some temporary buffer space, as pb pushes BACK and substitution + * proceeds forward... */ +static char *buffer; +static size_t bufsize = 0; +static size_t current = 0; + +static void addchars(const char *, size_t); +static void addchar(int); +static char *twiddle(const char *); +static char *getstring(void); +static void exit_regerror(int, const char *, regex_t *) __dead; +static void do_subst(const char *, const char *, regex_t *, const char *, + regmatch_t *); +static void do_regexpindex(const char *, const char *, regex_t *, regmatch_t *); +static void do_regexp(const char *, const char *, regex_t *, const char *, regmatch_t *); +static void add_sub(size_t, const char *, regex_t *, regmatch_t *); +static void add_replace(const char *, regex_t *, const char *, regmatch_t *); +#define addconstantstring(s) addchars((s), sizeof(s)-1) + +static void +addchars(const char *c, size_t n) +{ + if (n == 0) + return; + while (current + n > bufsize) { + if (bufsize == 0) + bufsize = 1024; + else + bufsize *= 2; + buffer = xrealloc(buffer, bufsize, NULL); + } + memcpy(buffer+current, c, n); + current += n; +} + +static void +addchar(int c) +{ + if (current +1 > bufsize) { + if (bufsize == 0) + bufsize = 1024; + else + bufsize *= 2; + buffer = xrealloc(buffer, bufsize, NULL); + } + buffer[current++] = c; +} + +static char * +getstring(void) +{ + addchar('\0'); + current = 0; + return buffer; +} + + +static void +exit_regerror(int er, const char *pat, regex_t *re) +{ + size_t errlen; + char *errbuf; + + errlen = regerror(er, re, NULL, 0); + errbuf = xalloc(errlen, + "malloc in regerror: %lu", (unsigned long)errlen); + regerror(er, re, errbuf, errlen); + m4errx(1, "regular expression error: %s for: `%s'", errbuf, pat); +} + +static void +add_sub(size_t n, const char *string, regex_t *re, regmatch_t *pm) +{ + if (n > re->re_nsub) { + if (!quiet) + warnx("No subexpression %zu", n); + if (fatal_warnings) + exit(EXIT_FAILURE); + } + /* Subexpressions that did not match are + * not an error. */ + else if (pm[n].rm_so != -1 && + pm[n].rm_eo != -1) { + addchars(string + pm[n].rm_so, + pm[n].rm_eo - pm[n].rm_so); + } +} + +/* Add replacement string to the output buffer, recognizing special + * constructs and replacing them with substrings of the original string. + */ +static void +add_replace(const char *string, regex_t *re, const char *replace, regmatch_t *pm) +{ + const char *p; + + for (p = replace; *p != '\0'; p++) { + if (*p == '&' && !mimic_gnu) { + add_sub(0, string, re, pm); + continue; + } + if (*p == '\\') { + if (p[1] == '\\') { + addchar(p[1]); + p++; + continue; + } + if (p[1] == '&') { + if (mimic_gnu) + add_sub(0, string, re, pm); + else + addchar(p[1]); + p++; + continue; + } + if (isdigit((unsigned char)p[1])) { + add_sub(*(++p) - '0', string, re, pm); + continue; + } + } + addchar(*p); + } +} + +static void +do_subst(const char *pat, const char *string, regex_t *re, const char *replace, + regmatch_t *pm) +{ + int error; + int flags = 0; + const char *last_match = NULL; + + while ((error = regexec(re, string, re->re_nsub+1, pm, flags)) == 0) { + if (pm[0].rm_eo != 0) { + if (string[pm[0].rm_eo-1] == '\n') + flags = 0; + else + flags = REG_NOTBOL; + } + + /* NULL length matches are special... We use the `vi-mode' + * rule: don't allow a NULL-match at the last match + * position. + */ + if (pm[0].rm_so == pm[0].rm_eo && + string + pm[0].rm_so == last_match) { + if (*string == '\0') + return; + addchar(*string); + if (*string++ == '\n') + flags = 0; + else + flags = REG_NOTBOL; + continue; + } + last_match = string + pm[0].rm_so; + addchars(string, pm[0].rm_so); + add_replace(string, re, replace, pm); + string += pm[0].rm_eo; + buffer[current] = '\0'; + } + while (*string) + addchar(*string++); + if (error != REG_NOMATCH) + exit_regerror(error, pat, re); + pbstr(string); +} + +static void +do_regexp(const char *pat, const char *string, regex_t *re, const char *replace, + regmatch_t *pm) +{ + int error; + + switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) { + case 0: + add_replace(string, re, replace, pm); + pbstr(getstring()); + break; + case REG_NOMATCH: + break; + default: + exit_regerror(error, pat, re); + } +} + +static void +do_regexpindex(const char *pat, const char *string, regex_t *re, regmatch_t *pm) +{ + int error; + + switch(error = regexec(re, string, re->re_nsub+1, pm, 0)) { + case 0: + pbunsigned(pm[0].rm_so); + break; + case REG_NOMATCH: + pbnum(-1); + break; + default: + exit_regerror(error, pat, re); + } +} + +/* In Gnu m4 mode, parentheses for backmatch don't work like POSIX 1003.2 + * says. So we twiddle with the regexp before passing it to regcomp. + */ +static char * +twiddle(const char *p) +{ + /* + at start of regexp is a normal character for Gnu m4 */ + if (*p == '^') { + addchar(*p); + p++; + } + if (*p == '+') { + addchar('\\'); + } + /* This could use strcspn for speed... */ + while (*p != '\0') { + if (*p == '\\') { + switch(p[1]) { + case '(': + case ')': + case '|': + addchar(p[1]); + break; + case 'w': + addconstantstring("[_a-zA-Z0-9]"); + break; + case 'W': + addconstantstring("[^_a-zA-Z0-9]"); + break; + case '<': + addconstantstring("[[:<:]]"); + break; + case '>': + addconstantstring("[[:>:]]"); + break; + default: + addchars(p, 2); + break; + } + p+=2; + continue; + } + if (*p == '(' || *p == ')' || *p == '|') + addchar('\\'); + + addchar(*p); + p++; + } + return getstring(); +} + +static int +checkempty(const char *argv[], int argc) +{ + const char *s; + size_t len; + + if (argc != 3 && argv[3][0] != '\0') + return 0; + + if (argc == 3) { + if (!quiet) + warnx("Too few arguments to patsubst"); + if (fatal_warnings) + exit(EXIT_FAILURE); + } + + if (argv[4] && argc > 4) + len = strlen(argv[4]); + else + len = 0; + for (s = argv[2]; *s != '\0'; s++) { + addchars(argv[4], len); + addchar(*s); + } + return 1; +} + +/* patsubst(string, regexp, opt replacement) */ +/* argv[2]: string + * argv[3]: regexp + * argv[4]: opt rep + */ +void +dopatsubst(const char *argv[], int argc) +{ + if (argc < 3) { + if (!quiet) + warnx("Too few arguments to patsubst"); + if (fatal_warnings) + exit(EXIT_FAILURE); + return; + } + /* special case: empty regexp */ + if (!checkempty(argv, argc)) { + + const char *pat; + int error; + regex_t re; + regmatch_t *pmatch; + int mode = REG_EXTENDED; + size_t l = strlen(argv[3]); + + if (!mimic_gnu || + (argv[3][0] == '^') || + (l > 0 && argv[3][l-1] == '$')) + mode |= REG_NEWLINE; + + pat = mimic_gnu ? twiddle(argv[3]) : argv[3]; + error = regcomp(&re, pat, mode); + if (error != 0) + exit_regerror(error, pat, &re); + + pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL); + do_subst(pat, argv[2], &re, + argc > 4 && argv[4] != NULL ? argv[4] : "", pmatch); + free(pmatch); + regfree(&re); + } + pbstr(getstring()); +} + +void +doregexp(const char *argv[], int argc) +{ + int error; + regex_t re; + regmatch_t *pmatch; + const char *pat; + + if (argc < 3) { + if (!quiet) + warnx("Too few arguments to regexp"); + if (fatal_warnings) + exit(EXIT_FAILURE); + return; + } + if (checkempty(argv, argc)) { + return; + } + + pat = mimic_gnu ? twiddle(argv[3]) : argv[3]; + error = regcomp(&re, pat, REG_EXTENDED); + if (error != 0) + exit_regerror(error, pat, &re); + + pmatch = xalloc(sizeof(regmatch_t) * (re.re_nsub+1), NULL); + if (argv[4] == NULL || argc == 4) + do_regexpindex(pat, argv[2], &re, pmatch); + else + do_regexp(pat, argv[2], &re, argv[4], pmatch); + free(pmatch); + regfree(&re); +} + +void +doformat(const char *argv[], int argc) +{ + const char *format = argv[2]; + int pos = 3; + int left_padded; + long width; + size_t l; + const char *thisarg; + char temp[2]; + size_t extra; + + while (*format != 0) { + if (*format != '%') { + addchar(*format++); + continue; + } + + format++; + if (*format == '%') { + addchar(*format++); + continue; + } + if (*format == 0) { + addchar('%'); + break; + } + + if (*format == '*') { + format++; + if (pos >= argc) + m4errx(1, + "Format with too many format specifiers."); + width = strtol(argv[pos++], NULL, 10); + } else { + char *eformat; + width = strtol(format, &eformat, 10); + format = eformat; + } + if (width < 0) { + left_padded = 1; + width = -width; + } else { + left_padded = 0; + } + if (*format == '.') { + format++; + if (*format == '*') { + format++; + if (pos >= argc) + m4errx(1, + "Format with too many format specifiers."); + extra = strtol(argv[pos++], NULL, 10); + } else { + char *eformat; + extra = strtol(format, &eformat, 10); + format = eformat; + } + } else { + extra = SIZE_T_MAX; + } + if (pos >= argc) + m4errx(1, "Format with too many format specifiers."); + switch(*format) { + case 's': + thisarg = argv[pos++]; + break; + case 'c': + temp[0] = strtoul(argv[pos++], NULL, 10); + temp[1] = 0; + thisarg = temp; + break; + default: + m4errx(1, "Unsupported format specification: %s.", + argv[2]); + } + format++; + l = strlen(thisarg); + if (l > extra) + l = extra; + if (!left_padded) { + while (l < (size_t)width--) + addchar(' '); + } + addchars(thisarg, l); + if (left_padded) { + while (l < (size_t)width--) + addchar(' '); + } + } + pbstr(getstring()); +} + +void +doesyscmd(const char *cmd) +{ + int p[2]; + pid_t pid, cpid; + const char *argv[4]; + int cc; + int status; + + /* Follow gnu m4 documentation: first flush buffers. */ + fflush(NULL); + + argv[0] = "sh"; + argv[1] = "-c"; + argv[2] = cmd; + argv[3] = NULL; + + /* Just set up standard output, share stderr and stdin with m4 */ + if (pipe(p) == -1) + err(1, "bad pipe"); + switch(cpid = fork()) { + case -1: + err(1, "bad fork"); + /* NOTREACHED */ + case 0: + (void) close(p[0]); + (void) dup2(p[1], 1); + (void) close(p[1]); + execv(_PATH_BSHELL, __UNCONST(argv)); + exit(1); + default: + /* Read result in two stages, since m4's buffer is + * pushback-only. */ + (void) close(p[1]); + do { + char result[BUFSIZE]; + cc = read(p[0], result, sizeof result); + if (cc > 0) + addchars(result, cc); + } while (cc > 0 || (cc == -1 && errno == EINTR)); + + (void) close(p[0]); + while ((pid = wait(&status)) != cpid && pid >= 0) + continue; + pbstr(getstring()); + } +} + +void +getdivfile(const char *name) +{ + FILE *f; + int c; + + f = fopen(name, "r"); + if (!f) + return; + + while ((c = getc(f))!= EOF) + putc(c, active); + (void) fclose(f); +} + +#ifdef REAL_FREEZE +void +freeze_state(const char *fname) +{ + FILE *f; + + if ((f = fopen(fname, "wb")) == NULL) + m4errx(EXIT_FAILURE, "Can't open output freeze file `%s' (%s)", + fname, strerror(errno)); + fprintf(f, "# This is a frozen state file generated by %s\nV1\n", + getprogname()); + fprintf(f, "Q%zu,%zu\n%s%s\n", strlen(lquote), strlen(rquote), + lquote, rquote); + fprintf(f, "C%zu,%zu\n%s%s\n", strlen(scommt), strlen(ecommt), + scommt, ecommt); + dump_state(f); + /* XXX: diversions? */ + fprintf(f, "D-1,0\n"); + fprintf(f, "# End of frozen state file\n"); + fclose(f); +} + +void +thaw_state(const char *fname) +{ + char *name = NULL; + size_t nl, namelen = 0; + char *defn = NULL; + size_t dl, defnlen = 0; + size_t lineno = 0; + char line[1024], *ptr, type; + FILE *f; + + if ((f = fopen(fname, "rb")) == NULL) + m4errx(EXIT_FAILURE, "Can't open frozen file `%s' (%s)", + fname, strerror(errno)); + +#define GET() if (fgets(line, (int)sizeof(line), f) == NULL) goto out +#define GETSTR(s, l) if (fread(s, 1, l, f) != l) goto out; else s[l] = '\0' + + GET(); /* comment */ + GET(); /* version */ + if ((ptr = strrchr(line, '\n')) != NULL) + *ptr = '\0'; + if (strcmp(line, "V1") != 0) + m4errx(EXIT_FAILURE, "Bad frozen version `%s'", line); + + for (;;) { + GET(); + lineno++; + switch (*line) { + case '\n': + continue; + case '#': + free(name); + free(defn); + fclose(f); + return; + default: + if (sscanf(line, "%c%zu,%zu\n", &type, &nl, &dl) != 3) + m4errx(EXIT_FAILURE, "%s, %zu: Bad line `%s'", + fname, lineno, line); + break; + } + + switch (type) { + case 'Q': + if (nl >= sizeof(lquote) || dl >= sizeof(rquote)) + m4errx(EXIT_FAILURE, "%s, %zu: Quote too long", + fname, lineno); + GETSTR(lquote, nl); + GETSTR(rquote, dl); + break; + + case 'C': + if (nl >= sizeof(scommt) || dl >= sizeof(ecommt)) + m4errx(EXIT_FAILURE, "%s, %zu: Comment too long", + fname, lineno); + GETSTR(scommt, nl); + GETSTR(ecommt, dl); + break; + + case 'T': + case 'F': + if (nl >= namelen) + name = xrealloc(name, namelen = nl + 1, + "name grow"); + if (dl >= defnlen) + defn = xrealloc(defn, defnlen = dl + 1, + "defn grow"); + GETSTR(name, nl); + GETSTR(defn, dl); + macro_pushdef(name, defn); + break; + + case 'D': + /* XXX: Not implemented */ + break; + + default: + m4errx(EXIT_FAILURE, "%s, %zu: Unknown type %c", + fname, lineno,type); + } + } +out: + m4errx(EXIT_FAILURE, "Unexprected end of file in `%s'", fname); +} +#endif diff --git a/usr.bin/m4/lib/ohash.h b/usr.bin/m4/lib/ohash.h new file mode 100644 index 0000000..8ccdaf9 --- /dev/null +++ b/usr.bin/m4/lib/ohash.h @@ -0,0 +1,73 @@ +#ifndef OHASH_H +#define OHASH_H +/* $OpenBSD: ohash.h,v 1.8 2005/12/29 18:54:47 jaredy Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* Open hashing support. + * Open hashing was chosen because it is much lighter than other hash + * techniques, and more efficient in most cases. + */ + +struct ohash_info { + ptrdiff_t key_offset; + void *data; /* user data */ + void *(*halloc)(size_t, void *); + void (*hfree)(void *, size_t, void *); + void *(*alloc)(size_t, void *); +}; + +struct _ohash_record; + +struct ohash { + struct _ohash_record *t; + struct ohash_info info; + unsigned int size; + unsigned int total; + unsigned int deleted; +}; + +/* For this to be tweakable, we use small primitives, and leave part of the + * logic to the client application. e.g., hashing is left to the client + * application. We also provide a simple table entry lookup that yields + * a hashing table index (opaque) to be used in find/insert/remove. + * The keys are stored at a known position in the client data. + */ +__BEGIN_DECLS +void ohash_init(struct ohash *, unsigned, struct ohash_info *); +void ohash_delete(struct ohash *); + +unsigned int ohash_lookup_interval(struct ohash *, const char *, + const char *, u_int32_t); +unsigned int ohash_lookup_memory(struct ohash *, const char *, + size_t, u_int32_t); +void *ohash_find(struct ohash *, unsigned int); +void *ohash_remove(struct ohash *, unsigned int); +void *ohash_insert(struct ohash *, unsigned int, void *); +void *ohash_first(struct ohash *, unsigned int *); +void *ohash_next(struct ohash *, unsigned int *); +unsigned int ohash_entries(struct ohash *); + +void *ohash_create_entry(struct ohash_info *, const char *, const char **); +u_int32_t ohash_interval(const char *, const char **); + +unsigned int ohash_qlookupi(struct ohash *, const char *, const char **); +unsigned int ohash_qlookup(struct ohash *, const char *); +__END_DECLS +#endif + diff --git a/usr.bin/m4/lib/ohash_create_entry.c b/usr.bin/m4/lib/ohash_create_entry.c new file mode 100644 index 0000000..3bdc9ab --- /dev/null +++ b/usr.bin/m4/lib/ohash_create_entry.c @@ -0,0 +1,38 @@ +/* $OpenBSD: ohash_create_entry.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +/* This handles the common case of variable length keys, where the + * key is stored at the end of the record. + */ +void * +ohash_create_entry(struct ohash_info *i, const char *start, const char **end) +{ + char *p; + + if (!*end) + *end = start + strlen(start); + p = (i->alloc)(i->key_offset + (*end - start) + 1, i->data); + if (p) { + memcpy(p+i->key_offset, start, *end-start); + p[i->key_offset + (*end - start)] = '\0'; + } + return (void *)p; +} diff --git a/usr.bin/m4/lib/ohash_delete.c b/usr.bin/m4/lib/ohash_delete.c new file mode 100644 index 0000000..12a9c0c --- /dev/null +++ b/usr.bin/m4/lib/ohash_delete.c @@ -0,0 +1,31 @@ +/* $OpenBSD: ohash_delete.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" +/* hash_delete only frees the hash structure. Use hash_first/hash_next + * to free entries as well. */ +void +ohash_delete(struct ohash *h) +{ + (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size, + h->info.data); +#ifndef NDEBUG + h->t = NULL; +#endif +} diff --git a/usr.bin/m4/lib/ohash_do.c b/usr.bin/m4/lib/ohash_do.c new file mode 100644 index 0000000..2b8467d --- /dev/null +++ b/usr.bin/m4/lib/ohash_do.c @@ -0,0 +1,111 @@ +/* $OpenBSD: ohash_do.c,v 1.4 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +static void ohash_resize(struct ohash *); + +static void +ohash_resize(struct ohash *h) +{ + struct _ohash_record *n; + unsigned int ns, j; + unsigned int i, incr; + + if (4 * h->deleted < h->total) + ns = h->size << 1; + else if (3 * h->deleted > 2 * h->total) + ns = h->size >> 1; + else + ns = h->size; + if (ns < MINSIZE) + ns = MINSIZE; +#ifdef STATS_HASH + STAT_HASH_EXPAND++; + STAT_HASH_SIZE += ns - h->size; +#endif + n = (h->info.halloc)(sizeof(struct _ohash_record) * ns, h->info.data); + if (!n) + return; + + for (j = 0; j < h->size; j++) { + if (h->t[j].p != NULL && h->t[j].p != DELETED) { + i = h->t[j].hv % ns; + incr = ((h->t[j].hv % (ns - 2)) & ~1) + 1; + while (n[i].p != NULL) { + i += incr; + if (i >= ns) + i -= ns; + } + n[i].hv = h->t[j].hv; + n[i].p = h->t[j].p; + } + } + (h->info.hfree)(h->t, sizeof(struct _ohash_record) * h->size, + h->info.data); + h->t = n; + h->size = ns; + h->total -= h->deleted; + h->deleted = 0; +} + +void * +ohash_remove(struct ohash *h, unsigned int i) +{ + void *result = __UNCONST(h->t[i].p); + + if (result == NULL || result == DELETED) + return NULL; + +#ifdef STATS_HASH + STAT_HASH_ENTRIES--; +#endif + h->t[i].p = DELETED; + h->deleted++; + if (h->deleted >= MINDELETED && 4 * h->deleted > h->total) + ohash_resize(h); + return result; +} + +void * +ohash_find(struct ohash *h, unsigned int i) +{ + if (h->t[i].p == DELETED) + return NULL; + else + return __UNCONST(h->t[i].p); +} + +void * +ohash_insert(struct ohash *h, unsigned int i, void *p) +{ +#ifdef STATS_HASH + STAT_HASH_ENTRIES++; +#endif + if (h->t[i].p == DELETED) { + h->deleted--; + h->t[i].p = p; + } else { + h->t[i].p = p; + /* Arbitrary resize boundary. Tweak if not efficient enough. */ + if (++h->total * 4 > h->size * 3) + ohash_resize(h); + } + return p; +} diff --git a/usr.bin/m4/lib/ohash_entries.c b/usr.bin/m4/lib/ohash_entries.c new file mode 100644 index 0000000..683e204 --- /dev/null +++ b/usr.bin/m4/lib/ohash_entries.c @@ -0,0 +1,26 @@ +/* $OpenBSD: ohash_entries.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +unsigned int +ohash_entries(struct ohash *h) +{ + return h->total - h->deleted; +} diff --git a/usr.bin/m4/lib/ohash_enum.c b/usr.bin/m4/lib/ohash_enum.c new file mode 100644 index 0000000..966daa1 --- /dev/null +++ b/usr.bin/m4/lib/ohash_enum.c @@ -0,0 +1,36 @@ +/* $OpenBSD: ohash_enum.c,v 1.3 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +void * +ohash_first(struct ohash *h, unsigned int *pos) +{ + *pos = 0; + return ohash_next(h, pos); +} + +void * +ohash_next(struct ohash *h, unsigned int *pos) +{ + for (; *pos < h->size; (*pos)++) + if (h->t[*pos].p != DELETED && h->t[*pos].p != NULL) + return __UNCONST(h->t[(*pos)++].p); + return NULL; +} diff --git a/usr.bin/m4/lib/ohash_init.3 b/usr.bin/m4/lib/ohash_init.3 new file mode 100644 index 0000000..a0939a8 --- /dev/null +++ b/usr.bin/m4/lib/ohash_init.3 @@ -0,0 +1,229 @@ +.\" $OpenBSD: ohash_init.3,v 1.14 2007/05/31 19:19:30 jmc Exp $ +.\" Copyright (c) 1999 Marc Espie +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 31 2007 $ +.Dt OPEN_HASH 3 +.Os +.Sh NAME +.Nm ohash_init , +.Nm ohash_delete , +.Nm ohash_lookup_interval , +.Nm ohash_lookup_memory , +.Nm ohash_find , +.Nm ohash_remove , +.Nm ohash_insert , +.Nm ohash_first , +.Nm ohash_next , +.Nm ohash_entries +.Nd light-weight open hashing +.Sh SYNOPSIS +.Fd #include +.Fd #include +.Fd #include +.Ft void +.Fn ohash_init "struct ohash *h" "unsigned int size" "struct ohash_info *info" +.Ft void +.Fn ohash_delete "struct ohash *h" +.Ft "unsigned int" +.Fn ohash_lookup_interval "struct ohash *h" "const char *start" "const char *end" "uint32_t hv" +.Ft "unsigned int" +.Fn ohash_lookup_memory "struct ohash *h" "const char *k" "size_t s" "uint32_t hv" +.Ft void * +.Fn ohash_find "struct ohash *h" "unsigned int i" +.Ft void * +.Fn ohash_remove "struct ohash *h" "unsigned int i" +.Ft void * +.Fn ohash_insert "struct ohash *h" "unsigned int i" "void *p" +.Ft void * +.Fn ohash_first "struct ohash *h" "unsigned int *i" +.Ft void * +.Fn ohash_next "struct ohash *h" "unsigned int *i" +.Ft "unsigned int" +.Fn ohash_entries "struct ohash *h" +.Sh DESCRIPTION +These functions have been designed as a fast, extensible alternative to +the usual hash table functions. +They provide storage and retrieval of records indexed by keys, +where a key is a contiguous sequence of bytes at a fixed position in +each record. +Keys can either be NUL-terminated strings or fixed-size memory areas. +All functions take a pointer to an ohash structure as the +.Fa h +function argument. +Storage for this structure should be provided by user code. +.Pp +.Fn ohash_init +initializes the table to store roughly 2 to the power +.Fa size +elements. +.Fa info +holds the position of the key in each record, and two pointers to +.Xr calloc 3 +and +.Xr free 3 Ns -like +functions, to use for managing the table internal storage. +.Pp +.Fn ohash_delete +frees storage internal to +.Fa h . +Elements themselves should be freed by the user first, using for instance +.Fn ohash_first +and +.Fn ohash_next . +.Pp +.Fn ohash_lookup_interval +and +.Fn ohash_lookup_memory +are the basic look-up element functions. +The hashing function result is provided by the user as +.Fa hv . +These return a +.Qq slot +in the ohash table +.Fa h , +to be used with +.Fn ohash_find , +.Fn ohash_insert , +or +.Fn ohash_remove . +This slot is only valid up to the next call to +.Fn ohash_insert +or +.Fn ohash_remove . +.Pp +.Fn ohash_lookup_interval +handles string-like keys. +.Fn ohash_lookup_interval +assumes the key is the interval between +.Fa start +and +.Fa end , +exclusive, +though the actual elements stored in the table should only contain +NUL-terminated keys. +.Pp +.Fn ohash_lookup_memory +assumes the key is the memory area starting at +.Fa k +of size +.Fa s . +All bytes are significant in key comparison. +.Pp +.Fn ohash_find +retrieves an element from a slot +.Fa i +returned by the +.Fn ohash_lookup* +functions. +It returns +.Dv NULL +if the slot is empty. +.Pp +.Fn ohash_insert +inserts a new element +.Fa p +at slot +.Fa i . +Slot +.Fa i +must be empty and element +.Fa p +must have a key corresponding to the +.Fn ohash_lookup* +call. +.Pp +.Fn ohash_remove +removes the element at slot +.Fa i . +It returns the removed element, for user code to dispose of, or +.Dv NULL +if the slot was empty. +.Pp +.Fn ohash_first +and +.Fn ohash_next +can be used to access all elements in an ohash table, like this: +.Bd -literal -offset indent +for (n = ohash_first(h, &i); n != NULL; n = ohash_next(h, &i)) + do_something_with(n); +.Ed +.Pp +.Fa i +points to an auxiliary unsigned integer used to record the current position +in the ohash table. +Those functions are safe to use even while entries are added to/removed +from the table, but in such a case they don't guarantee that new entries +will be returned. +As a special case, they can safely be used to free elements in the table. +.Pp +.Fn ohash_entries +returns the number of elements in the hash table. +.Sh STORAGE HANDLING +Only +.Fn ohash_init , +.Fn ohash_insert , +.Fn ohash_remove +and +.Fn ohash_delete +may call the user-supplied memory functions. +It is the responsibility of the user memory allocation code to verify +that those calls did not fail. +.Pp +If memory allocation fails, +.Fn ohash_init +returns a useless hash table. +.Fn ohash_insert +and +.Fn ohash_remove +still perform the requested operation, but the returned table should be +considered read-only. +It can still be accessed by +.Fn ohash_lookup* , +.Fn ohash_find , +.Fn ohash_first +and +.Fn ohash_next +to dump relevant information to disk before aborting. +.Sh THREAD SAFETY +The open hashing functions are not thread-safe by design. +In particular, in a threaded environment, there is no guarantee that a +.Qq slot +will not move between a +.Fn ohash_lookup* +and a +.Fn ohash_find , +.Fn ohash_insert +or +.Fn ohash_remove +call. +.Pp +Multi-threaded applications should explicitly protect ohash table access. +.Sh SEE ALSO +.Xr ohash_interval 3 +.Rs +.%A Donald E. Knuth +.%B The Art of Computer Programming +.%V Vol. 3 +.%P pp 506-550 +.%D 1973 +.Re +.Sh STANDARDS +Those functions are completely non-standard and should be avoided in +portable programs. +.Sh HISTORY +Those functions were designed and written for +.Ox +.Xr make 1 +by Marc Espie in 1999. diff --git a/usr.bin/m4/lib/ohash_init.c b/usr.bin/m4/lib/ohash_init.c new file mode 100644 index 0000000..4d24fa4 --- /dev/null +++ b/usr.bin/m4/lib/ohash_init.c @@ -0,0 +1,41 @@ +/* $OpenBSD: ohash_init.c,v 1.2 2004/06/22 20:00:16 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +void +ohash_init(struct ohash *h, unsigned int size, struct ohash_info *info) +{ + h->size = 1UL << size; + if (h->size < MINSIZE) + h->size = MINSIZE; +#ifdef STATS_HASH + STAT_HASH_CREATION++; + STAT_HASH_SIZE += h->size; +#endif + /* Copy info so that caller may free it. */ + h->info.key_offset = info->key_offset; + h->info.halloc = info->halloc; + h->info.hfree = info->hfree; + h->info.alloc = info->alloc; + h->info.data = info->data; + h->t = (h->info.halloc)(sizeof(struct _ohash_record) * h->size, + h->info.data); + h->total = h->deleted = 0; +} diff --git a/usr.bin/m4/lib/ohash_int.h b/usr.bin/m4/lib/ohash_int.h new file mode 100644 index 0000000..88c818b --- /dev/null +++ b/usr.bin/m4/lib/ohash_int.h @@ -0,0 +1,23 @@ +/* $OpenBSD: ohash_int.h,v 1.3 2006/01/16 15:52:25 espie Exp $ */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#include +#include +#include +#include "ohash.h" + +struct _ohash_record { + u_int32_t hv; + const char *p; +}; + +#define DELETED ((const char *)h) +#define NONE (h->size) + +/* Don't bother changing the hash table if the change is small enough. */ +#define MINSIZE (1UL << 4) +#define MINDELETED 4 diff --git a/usr.bin/m4/lib/ohash_interval.3 b/usr.bin/m4/lib/ohash_interval.3 new file mode 100644 index 0000000..2e56ca5 --- /dev/null +++ b/usr.bin/m4/lib/ohash_interval.3 @@ -0,0 +1,90 @@ +.\" $OpenBSD: ohash_interval.3,v 1.11 2007/05/31 19:19:30 jmc Exp $ +.\" Copyright (c) 2001 Marc Espie +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: May 31 2007 $ +.Dt OPEN_HASH_HELPER 3 +.Os +.Sh NAME +.Nm ohash_interval , +.Nm ohash_create_entry , +.Nm ohash_qlookup , +.Nm ohash_qlookupi +.Nd helper functions for open hashing +.Sh SYNOPSIS +.Fd #include +.Fd #include +.Fd #include +.Ft u_int32_t +.Fn ohash_interval "const char *start" "const char **pend" +.Ft "void *" +.Fn ohash_create_entry "struct ohash_info *info" "const char *start" "const char **pend" +.Ft "unsigned int" +.Fn ohash_qlookupi "struct ohash *h" "const char *start" "const char **pend" +.Ft "unsigned int" +.Fn ohash_qlookup "struct ohash *h" "const char *start" +.Sh DESCRIPTION +These functions are commonly used to simplify open hashing usage, and use +similar conventions. +They operate indifferently on NUL-terminated strings +.Po +by setting +.Fa *pend += +.Dv NULL +.Pc +or memory ranges +.Po +delimited by +.Fa start +and +.Fa *pend +.Pc . +For NUL-terminated strings, as a side effect, those functions +set +.Fa *pend +to the terminating NUL byte. +.Pp +.Fn ohash_interval +is a simple hashing function that yields good results on common data sets. +.Pp +.Fn ohash_create_entry +can be used to create a new record with a given key. +In that case, +the alloc field of +.Fa info +should point to a +.Xr malloc 3 Ns -like +function to allocate the storage. +.Pp +.Fn ohash_qlookupi +is a wrapper function that simply calls +.Fn ohash_interval +and +.Fn ohash_lookup_interval . +.Pp +.Fn ohash_qlookup +is a variation on +.Fn ohash_qlookupi +designed for NUL-terminated strings. +.Sh SEE ALSO +.Xr ohash_init 3 +.Sh STANDARDS +Those functions are completely non-standard and should be avoided in +portable programs. +.Sh HISTORY +Those functions were designed and written for +.Ox +.Xr make 1 +by Marc Espie in 1999. diff --git a/usr.bin/m4/lib/ohash_interval.c b/usr.bin/m4/lib/ohash_interval.c new file mode 100644 index 0000000..2434d3b --- /dev/null +++ b/usr.bin/m4/lib/ohash_interval.c @@ -0,0 +1,36 @@ +/* $OpenBSD: ohash_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +uint32_t +ohash_interval(const char *s, const char **e) +{ + uint32_t k; + + if (!*e) + *e = s + strlen(s); + if (s == *e) + k = 0; + else + k = *s++; + while (s != *e) + k = ((k << 2) | (k >> 30)) ^ *s++; + return k; +} diff --git a/usr.bin/m4/lib/ohash_lookup_interval.c b/usr.bin/m4/lib/ohash_lookup_interval.c new file mode 100644 index 0000000..e497801 --- /dev/null +++ b/usr.bin/m4/lib/ohash_lookup_interval.c @@ -0,0 +1,68 @@ +/* $OpenBSD: ohash_lookup_interval.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +unsigned int +ohash_lookup_interval(struct ohash *h, const char *start, const char *end, + uint32_t hv) +{ + unsigned int i, incr; + unsigned int empty; + +#ifdef STATS_HASH + STAT_HASH_LOOKUP++; +#endif + empty = NONE; + i = hv % h->size; + incr = ((hv % (h->size-2)) & ~1) + 1; + while (h->t[i].p != NULL) { +#ifdef STATS_HASH + STAT_HASH_LENGTH++; +#endif + if (h->t[i].p == DELETED) { + if (empty == NONE) + empty = i; + } else if (h->t[i].hv == hv && + strncmp(h->t[i].p+h->info.key_offset, start, + end - start) == 0 && + (h->t[i].p+h->info.key_offset)[end-start] == '\0') { + if (empty != NONE) { + h->t[empty].hv = hv; + h->t[empty].p = h->t[i].p; + h->t[i].p = DELETED; + return empty; + } else { +#ifdef STATS_HASH + STAT_HASH_POSITIVE++; +#endif + return i; + } + } + i += incr; + if (i >= h->size) + i -= h->size; + } + + /* Found an empty position. */ + if (empty != NONE) + i = empty; + h->t[i].hv = hv; + return i; +} diff --git a/usr.bin/m4/lib/ohash_lookup_memory.c b/usr.bin/m4/lib/ohash_lookup_memory.c new file mode 100644 index 0000000..93ded5a --- /dev/null +++ b/usr.bin/m4/lib/ohash_lookup_memory.c @@ -0,0 +1,64 @@ +/* $OpenBSD: ohash_lookup_memory.c,v 1.3 2006/01/16 15:52:25 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +unsigned int +ohash_lookup_memory(struct ohash *h, const char *k, size_t size, uint32_t hv) +{ + unsigned int i, incr; + unsigned int empty; + +#ifdef STATS_HASH + STAT_HASH_LOOKUP++; +#endif + empty = NONE; + i = hv % h->size; + incr = ((hv % (h->size-2)) & ~1) + 1; + while (h->t[i].p != NULL) { +#ifdef STATS_HASH + STAT_HASH_LENGTH++; +#endif + if (h->t[i].p == DELETED) { + if (empty == NONE) + empty = i; + } else if (h->t[i].hv == hv && + memcmp(h->t[i].p+h->info.key_offset, k, size) == 0) { + if (empty != NONE) { + h->t[empty].hv = hv; + h->t[empty].p = h->t[i].p; + h->t[i].p = DELETED; + return empty; + } else { +#ifdef STATS_HASH + STAT_HASH_POSITIVE++; +#endif + } return i; + } + i += incr; + if (i >= h->size) + i -= h->size; + } + + /* Found an empty position. */ + if (empty != NONE) + i = empty; + h->t[i].hv = hv; + return i; +} diff --git a/usr.bin/m4/lib/ohash_qlookup.c b/usr.bin/m4/lib/ohash_qlookup.c new file mode 100644 index 0000000..58b3454 --- /dev/null +++ b/usr.bin/m4/lib/ohash_qlookup.c @@ -0,0 +1,27 @@ +/* $OpenBSD: ohash_qlookup.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +unsigned int +ohash_qlookup(struct ohash *h, const char *s) +{ + const char *e = NULL; + return ohash_qlookupi(h, s, &e); +} diff --git a/usr.bin/m4/lib/ohash_qlookupi.c b/usr.bin/m4/lib/ohash_qlookupi.c new file mode 100644 index 0000000..2acec4e --- /dev/null +++ b/usr.bin/m4/lib/ohash_qlookupi.c @@ -0,0 +1,29 @@ +/* $OpenBSD: ohash_qlookupi.c,v 1.2 2004/06/22 20:00:17 espie Exp $ */ +/* ex:ts=8 sw=4: + */ + +/* Copyright (c) 1999, 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "ohash_int.h" + +unsigned int +ohash_qlookupi(struct ohash *h, const char *s, const char **e) +{ + u_int32_t hv; + + hv = ohash_interval(s, e); + return ohash_lookup_interval(h, s, *e, hv); +} diff --git a/usr.bin/m4/lib/strtonum.c b/usr.bin/m4/lib/strtonum.c new file mode 100644 index 0000000..8d11712 --- /dev/null +++ b/usr.bin/m4/lib/strtonum.c @@ -0,0 +1,73 @@ +/* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ + +/* + * Copyright (c) 2004 Ted Unangst and Todd Miller + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: strtonum.c,v 1.2 2009/10/26 21:14:18 christos Exp $"); +#include +#include +#include + +#define INVALID 1 +#define TOOSMALL 2 +#define TOOLARGE 3 + +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp); +long long +strtonum(const char *numstr, long long minval, long long maxval, + const char **errstrp) +{ + long long ll = 0; + char *ep; + int error = 0; + struct errval { + const char *errstr; + int err; + } ev[4] = { + { NULL, 0 }, + { "invalid", EINVAL }, + { "too small", ERANGE }, + { "too large", ERANGE }, + }; + + ev[0].err = errno; + errno = 0; + if (minval > maxval) + error = INVALID; + else { + ll = strtoll(numstr, &ep, 10); + if (numstr == ep || *ep != '\0') + error = INVALID; + else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) + error = TOOSMALL; + else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) + error = TOOLARGE; + } + if (errstrp != NULL) + *errstrp = ev[error].errstr; + errno = ev[error].err; + if (error) + ll = 0; + + return (ll); +} + diff --git a/usr.bin/m4/look.c b/usr.bin/m4/look.c new file mode 100644 index 0000000..de2c282 --- /dev/null +++ b/usr.bin/m4/look.c @@ -0,0 +1,315 @@ +/* $NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $ */ +/* $OpenBSD: look.c,v 1.21 2009/10/14 17:23:17 sthen Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * look.c + * Facility: m4 macro processor + * by: oz + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: look.c,v 1.13 2016/01/16 17:00:07 christos Exp $"); +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" + +static void *hash_alloc(size_t, void *); +static void hash_free(void *, size_t, void *); +static void *element_alloc(size_t, void *); +static void setup_definition(struct macro_definition *, const char *, + const char *); + +static struct ohash_info macro_info = { + offsetof(struct ndblock, name), + NULL, hash_alloc, hash_free, element_alloc }; + +struct ohash macros; + +/* Support routines for hash tables. */ +void * +hash_alloc(size_t s, void *u UNUSED) +{ + void *storage = xalloc(s, "hash alloc"); + if (storage) + memset(storage, 0, s); + return storage; +} + +void +hash_free(void *p, size_t s UNUSED, void *u UNUSED) +{ + free(p); +} + +void * +element_alloc(size_t s, void *u UNUSED) +{ + return xalloc(s, "element alloc"); +} + +void +init_macros(void) +{ + ohash_init(¯os, 10, ¯o_info); +} + +/* + * find name in the hash table + */ +ndptr +lookup(const char *name) +{ + return ohash_find(¯os, ohash_qlookup(¯os, name)); +} + +struct macro_definition * +lookup_macro_definition(const char *name) +{ + ndptr p; + + p = ohash_find(¯os, ohash_qlookup(¯os, name)); + if (p) + return p->d; + else + return NULL; +} + +static void +setup_definition(struct macro_definition *d, const char *defn, const char *name) +{ + ndptr p; + + if (strncmp(defn, BUILTIN_MARKER, sizeof(BUILTIN_MARKER)-1) == 0 && + (p = macro_getbuiltin(defn+sizeof(BUILTIN_MARKER)-1)) != NULL) { + d->type = macro_builtin_type(p); + d->defn = xstrdup(defn+sizeof(BUILTIN_MARKER)-1); + } else { + if (!*defn) + d->defn = xstrdup(null); + else + d->defn = xstrdup(defn); + d->type = MACRTYPE; + } + if (STREQ(name, defn)) + d->type |= RECDEF; +} + +static ndptr +create_entry(const char *name) +{ + const char *end = NULL; + unsigned int i; + ndptr n; + + i = ohash_qlookupi(¯os, name, &end); + n = ohash_find(¯os, i); + if (n == NULL) { + n = ohash_create_entry(¯o_info, name, &end); + ohash_insert(¯os, i, n); + n->trace_flags = FLAG_NO_TRACE; + n->builtin_type = MACRTYPE; + n->d = NULL; + } + return n; +} + +void +macro_define(const char *name, const char *defn) +{ + ndptr n = create_entry(name); + if (n->d != NULL) { + if (n->d->defn != null) + free(n->d->defn); + } else { + n->d = xalloc(sizeof(struct macro_definition), NULL); + n->d->next = NULL; + } + setup_definition(n->d, defn, name); +} + +void +macro_pushdef(const char *name, const char *defn) +{ + ndptr n; + struct macro_definition *d; + + n = create_entry(name); + d = xalloc(sizeof(struct macro_definition), NULL); + d->next = n->d; + n->d = d; + setup_definition(n->d, defn, name); +} + +void +macro_undefine(const char *name) +{ + ndptr n = lookup(name); + if (n != NULL) { + struct macro_definition *r, *r2; + + for (r = n->d; r != NULL; r = r2) { + r2 = r->next; + if (r->defn != null) + free(r->defn); + free(r); + } + n->d = NULL; + } +} + +void +macro_popdef(const char *name) +{ + ndptr n = lookup(name); + + if (n != NULL) { + struct macro_definition *r = n->d; + if (r != NULL) { + n->d = r->next; + if (r->defn != null) + free(r->defn); + free(r); + } + } +} + +void +macro_for_all(void (*f)(const char *, struct macro_definition *)) +{ + ndptr n; + unsigned int i; + + for (n = ohash_first(¯os, &i); n != NULL; + n = ohash_next(¯os, &i)) + if (n->d != NULL) + f(n->name, n->d); +} + +void +setup_builtin(const char *name, unsigned int type) +{ + ndptr n; + char *name2; + + if (prefix_builtins) { + name2 = xalloc(strlen(name)+3+1, NULL); + memcpy(name2, "m4_", 3); + memcpy(name2 + 3, name, strlen(name)+1); + } else + name2 = xstrdup(name); + + n = create_entry(name2); + n->builtin_type = type; + n->d = xalloc(sizeof(struct macro_definition), NULL); + n->d->defn = name2; + n->d->type = type; + n->d->next = NULL; +} + +void +mark_traced(const char *name, int on) +{ + ndptr p; + unsigned int i; + + if (name == NULL) { + if (on) + trace_flags |= TRACE_ALL; + else + trace_flags &= ~TRACE_ALL; + for (p = ohash_first(¯os, &i); p != NULL; + p = ohash_next(¯os, &i)) + p->trace_flags = FLAG_NO_TRACE; + } else { + p = create_entry(name); + p->trace_flags = on; + } +} + +ndptr +macro_getbuiltin(const char *name) +{ + ndptr p; + + p = lookup(name); + if (p == NULL || p->builtin_type == MACRTYPE) + return NULL; + else + return p; +} + +#ifdef REAL_FREEZE +static void +recurse(FILE *f, ndptr n, struct macro_definition *d) +{ + if (d->next != NULL) + recurse(f, n, d->next); + + // skip built-ins, because it is cheaper to do so + // and initialize them manually + if (d->type & (NOARGS|NEEDARGS)) + return; + fprintf(f, "%c%zu,%zu\n%s%s\n", + (d->type & (NOARGS|NEEDARGS)) ? 'F' : 'T', + strlen(n->name), strlen(d->defn), + n->name, d->defn); +} + +static void +dump_entry(FILE *f, ndptr n) +{ + if (n->d == NULL) + return; + recurse(f, n, n->d); +} + +void +dump_state(FILE *f) +{ + ndptr n; + unsigned int i; + for (n = ohash_first(¯os, &i); n != NULL; + n = ohash_next(¯os, &i)) + dump_entry(f, n); +} +#endif diff --git a/usr.bin/m4/m4.1 b/usr.bin/m4/m4.1 new file mode 100644 index 0000000..f033cee --- /dev/null +++ b/usr.bin/m4/m4.1 @@ -0,0 +1,522 @@ +.\" $NetBSD: m4.1,v 1.27 2016/01/17 11:24:28 wiz Exp $ +.\" @(#) $OpenBSD: m4.1,v 1.56 2009/10/14 17:19:47 sthen Exp $ +.\" +.\" Copyright (c) 1989, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Ozan Yigit at York University. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd January 16, 2015 +.Dt M4 1 +.Os +.Sh NAME +.Nm m4 +.Nd macro language processor +.Sh SYNOPSIS +.Nm m4 +.Op Fl EGgiPQsv +.Oo +.Sm off +.Fl D Ar name Op No = Ar value +.Sm on +.Oc +.Op Fl d Ar flags +.Op Fl e Ar filename +.Op Fl F Ar filename +.Op Fl I Ar dirname +.Op Fl L Ar number +.Op Fl o Ar filename +.Op Fl R Ar filename +.Op Fl t Ar macro +.Op Fl U Ns Ar name +.Op Ar +.Sh DESCRIPTION +The +.Nm m4 +utility is a macro processor that can be used as a front end to any +language (e.g., C, ratfor, fortran, lex, and yacc). +If no input files are given, +.Nm m4 +reads from the standard input, +otherwise files specified on the command line are +processed in the given order. +Input files can be regular files, files in the m4 include paths, or a +single dash +.Pq Sq - , +denoting standard input. +.Nm m4 +writes +the processed text to the standard output, unless told otherwise. +.Pp +Macro calls have the form name(argument1[, argument2, ..., argumentN]). +.Pp +There cannot be any space following the macro name and the open +parenthesis +.Sq \&( . +If the macro name is not followed by an open +parenthesis it is processed with no arguments. +.Pp +Macro names consist of a leading alphabetic or underscore +possibly followed by alphanumeric or underscore characters, e.g., +valid macro names match the pattern +.Dq [a-zA-Z_][a-zA-Z0-9_]* . +.Pp +In arguments to macros, leading unquoted space, tab, and newline +.Pq Sq \en +characters are ignored. +To quote strings, use left and right single quotes +.Po e.g.,\ \& +.Sq "\ this is a string with a leading space" +.Pc . +You can change the quote characters with the +.Ic changequote +built-in macro. +.Pp +Most built-ins don't make any sense without arguments, and hence are not +recognized as special when not followed by an open parenthesis. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl D , Fl Fl define Ar name Ns Op Pf = Ns Ar value +Define the symbol +.Ar name +to have some value (or +.Dv NULL ) . +.It Fl d , Fl Fl debug Ar "flags" +Set trace flags. +.Ar flags +may hold the following: +.Bl -tag -width Ds +.It Ar a +print macro arguments. +.It Ar c +print macro expansion over several lines. +.It Ar e +print result of macro expansion. +.It Ar f +print filename location. +.It Ar l +print line number. +.It Ar q +quote arguments and expansion with the current quotes. +.It Ar t +start with all macros traced. +.It Ar x +number macro expansions. +.It Ar V +turn on all options. +.El +.Pp +By default, trace is set to +.Qq eq . +.It Fl E , Fl Fl fatal-warnings +Warnings make +.Nm +exit. +.It Fl e , Fl Fl error-output Ar filename +Redirect error output to filename. +.It Fl F , Fl Fl freeze-state Ar filename +Save the input state to +.Ar filename . +.It Fl G , Fl Fl traditional +Disable GNU-m4 extensions. +.It Fl g , Fl Fl gnu +Activate GNU-m4 compatibility mode. +In this mode, translit handles simple character +ranges (e.g., a-z), regular expressions mimic emacs behavior, +multiple m4wrap calls are handled as a stack, +the number of diversions is unlimited, +empty names for macro definitions are allowed, +and eval understands +.Sq 0rbase:value +numbers. +.It Fl Fl help +Print help message and exit. +.It Fl I , Fl Fl include Ar "dirname" +Add directory +.Ar dirname +to the include path. +.It Fl i , Fl Fl interactive +Set unbuffered output, disable tty signals. +.It Fl L , Fl Fl nesting-limit +Set the nesting limit in macro expansions. +This is unimplemented and unlimited. +.It Fl o Ar filename +Send trace output to +.Ar filename . +.It Fl P , Fl Fl prefix-builtins +Prefix all built-in macros with +.Sq m4_ . +For example, instead of writing +.Ic define , +use +.Ic m4_define . +.It Fl Q , Fl Fl quiet , Fl Fl silent +Don't print warnings. +.It Fl R , Fl Fl reload-state Ar filename +Reload a previously saved state from +.Ar filename . +.It Fl s , Fl Fl synclines +Output line synchronization directives, suitable for +.Xr cpp 1 . +.It Fl t , Fl Fl trace Ar macro +Turn tracing on for +.Ar macro . +.It Fl U , Fl Fl undefine Ar "name" +Undefine the symbol +.Ar name . +.It Fl v , Fl Fl version +Print the version and exit. +.El +.Sh SYNTAX +.Nm m4 +provides the following built-in macros. +They may be redefined, losing their original meaning. +Return values are null unless otherwise stated. +.Bl -tag -width changequote +.It Fn builtin name +Calls a built-in by its +.Fa name , +overriding possible redefinitions. +.It Fn changecom startcomment endcomment +Changes the start comment and end comment sequences. +Comment sequences may be up to five characters long. +The default values are the hash sign +and the newline character. +.Bd -literal -offset indent +# This is a comment +.Ed +.Pp +With no arguments, comments are turned off. +With one single argument, the end comment sequence is set +to the newline character. +.It Fn changequote beginquote endquote +Defines the open quote and close quote sequences. +Quote sequences may be up to five characters long. +The default values are the backquote character and the quote +character. +.Bd -literal -offset indent +`Here is a quoted string' +.Ed +.Pp +With no arguments, the default quotes are restored. +With one single argument, the close quote sequence is set +to the newline character. +.It Fn decr arg +Decrements the argument +.Fa arg +by 1. +The argument +.Fa arg +must be a valid numeric string. +.It Fn define name value +Define a new macro named by the first argument +.Fa name +to have the +value of the second argument +.Fa value . +Each occurrence of +.Sq $n +(where +.Ar n +is 0 through 9) is replaced by the +.Ar n Ns 'th +argument. +.Sq $0 +is the name of the calling macro. +Undefined arguments are replaced by a null string. +.Sq $# +is replaced by the number of arguments; +.Sq $* +is replaced by all arguments comma separated; +.Sq $@ +is the same as +.Sq $* +but all arguments are quoted against further expansion. +.It Fn defn name ... +Returns the quoted definition for each argument. +This can be used to rename +macro definitions (even for built-in macros). +.It Fn divert num +There are 10 output queues (numbered 0-9). +At the end of processing +.Nm m4 +concatenates all the queues in numerical order to produce the +final output. +Initially the output queue is 0. +The divert +macro allows you to select a new output queue (an invalid argument +passed to divert causes output to be discarded). +.It Ic divnum +Returns the current output queue number. +.It Ic dnl +Discard input characters up to and including the next newline. +.It Fn dumpdef name ... +Prints the names and definitions for the named items, or for everything +if no arguments are passed. +.It Fn errprint msg +Prints the first argument on the standard error output stream. +.It Fn esyscmd cmd +Passes its first argument to a shell and returns the shell's standard output. +Note that the shell shares its standard input and standard error with +.Nm m4 . +.It Fn eval expr[,radix[,minimum]] +Computes the first argument as an arithmetic expression using 32-bit +arithmetic. +Operators are the standard C ternary, arithmetic, logical, +shift, relational, bitwise, and parentheses operators. +You can specify +octal, decimal, and hexadecimal numbers as in C. +The optional second argument +.Fa radix +specifies the radix for the result and the optional third argument +.Fa minimum +specifies the minimum number of digits in the result. +.It Fn expr expr +This is an alias for +.Ic eval . +.It Fn format formatstring arg1 ... +Returns +.Fa formatstring +with escape sequences substituted with +.Fa arg1 +and following arguments, in a way similar to +.Xr printf 3 . +This built-in is only available in GNU-m4 compatibility mode, and the only +parameters implemented are there for autoconf compatibility: +left-padding flag, an optional field width, a maximum field width, +*-specified field widths, and the %s and %c data type. +.It Fn ifdef name yes no +If the macro named by the first argument is defined then return the second +argument, otherwise the third. +If there is no third argument, the value is +.Dv NULL . +The word +.Qq unix +is predefined. +.It Fn ifelse a b yes ... +If the first argument +.Fa a +matches the second argument +.Fa b +then +.Fn ifelse +returns +the third argument +.Fa yes . +If the match fails the three arguments are +discarded and the next three arguments are used until there is +zero or one arguments left, either this last argument or +.Dv NULL +is returned if no other matches were found. +.It Fn include name +Returns the contents of the file specified in the first argument. +If the file is not found as is, look through the include path: +first the directories specified with +.Fl I +on the command line, then the environment variable +.Ev M4PATH , +as a colon-separated list of directories. +Include aborts with an error message if the file cannot be included. +.It Fn incr arg +Increments the argument by 1. +The argument must be a valid numeric string. +.It Fn index string substring +Returns the index of the second argument in the first argument (e.g., +.Ic index(the quick brown fox jumped, fox) +returns 16). +If the second +argument is not found index returns \-1. +.It Fn indir macro arg1 ... +Indirectly calls the macro whose name is passed as the first argument, +with the remaining arguments passed as first, ... arguments. +.It Fn len arg +Returns the number of characters in the first argument. +Extra arguments +are ignored. +.It Fn m4exit code +Immediately exits with the return value specified by the first argument, +0 if none. +.It Fn m4wrap todo +Allows you to define what happens at the final +.Dv EOF , +usually for cleanup purposes (e.g., +.Ic m4wrap("cleanup(tempfile)") +causes the macro cleanup to be +invoked after all other processing is done). +.Pp +Multiple calls to +.Fn m4wrap +get inserted in sequence at the final +.Dv EOF . +.It Fn maketemp template +Invokes +.Xr mkstemp 3 +on the first argument, and returns the modified string. +This can be used to create unique +temporary file names. +.It Fn paste file +Includes the contents of the file specified by the first argument without +any macro processing. +Aborts with an error message if the file cannot be +included. +.It Fn patsubst string regexp replacement +Substitutes a regular expression in a string with a replacement string. +Usual substitution patterns apply: an ampersand +.Pq Sq \&& +is replaced by the string matching the regular expression. +The string +.Sq \e# , +where +.Sq # +is a digit, is replaced by the corresponding back-reference. +.It Fn popdef arg ... +Restores the +.Ic pushdef Ns ed +definition for each argument. +.It Fn pushdef macro def +Takes the same arguments as +.Ic define , +but it saves the definition on a +stack for later retrieval by +.Fn popdef . +.It Fn regexp string regexp replacement +Finds a regular expression in a string. +If no further arguments are given, +it returns the first match position or \-1 if no match. +If a third argument +is provided, it returns the replacement string, with sub-patterns replaced. +.It Fn shift arg1 ... +Returns all but the first argument, the remaining arguments are +quoted and pushed back with commas in between. +The quoting +nullifies the effect of the extra scan that will subsequently be +performed. +.It Fn sinclude file +Similar to +.Ic include , +except it ignores any errors. +.It Fn spaste file +Similar to +.Fn paste , +except it ignores any errors. +.It Fn substr string offset length +Returns a substring of the first argument starting at the offset specified +by the second argument and the length specified by the third argument. +If no third argument is present it returns the rest of the string. +.It Fn syscmd cmd +Passes the first argument to the shell. +Nothing is returned. +.It Ic sysval +Returns the return value from the last +.Ic syscmd . +.It Fn traceon arg ... +Enables tracing of macro expansions for the given arguments, or for all +macros if no argument is given. +.It Fn traceoff arg ... +Disables tracing of macro expansions for the given arguments, or for all +macros if no argument is given. +.It Fn translit string mapfrom mapto +Transliterate the characters in the first argument from the set +given by the second argument to the set given by the third. +You cannot use +.Xr tr 1 +style abbreviations. +.It Fn undefine name1 ... +Removes the definition for the macros specified by its arguments. +.It Fn undivert arg ... +Flushes the named output queues (or all queues if no arguments). +.It Ic unix +A pre-defined macro for testing the OS platform. +.It Ic __line__ +Returns the current file's line number. +.It Ic __file__ +Returns the current file's name. +.El +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2008 +specification. +.Pp +The flags +.Op Fl dgIot +and the macros +.Ic builtin , +.Ic esyscmd , +.Ic expr , +.Ic format , +.Ic indir , +.Ic paste , +.Ic patsubst , +.Ic regexp , +.Ic spaste , +.Ic unix , +.Ic __line__ , +and +.Ic __file__ +are extensions to that specification. +.Pp +The output format of tracing and of +.Ic dumpdef +are not specified in any standard, +are likely to change and should not be relied upon. +The current format of tracing is closely modelled on +.Nm gnu-m4 , +to allow +.Nm autoconf +to work. +.Pp +The built-ins +.Ic pushdef +and +.Ic popdef +handle macro definitions as a stack. +However, +.Ic define +interacts with the stack in an undefined way. +In this implementation, +.Ic define +replaces the top-most definition only. +Other implementations may erase all definitions on the stack instead. +.Pp +All built-ins do expand without arguments in many other +.Nm m4 . +.Pp +Many other +.Nm +have dire size limitations with respect to buffer sizes. +.Sh AUTHORS +.An -nosplit +.An Ozan Yigit Aq Mt oz@sis.yorku.ca +and +.An Richard A. O'Keefe Aq Mt ok@goanna.cs.rmit.OZ.AU . +.Pp +GNU-m4 compatibility extensions by +.An Marc Espie Aq Mt espie@cvs.openbsd.org . diff --git a/usr.bin/m4/main.c b/usr.bin/m4/main.c new file mode 100644 index 0000000..cba65ad --- /dev/null +++ b/usr.bin/m4/main.c @@ -0,0 +1,811 @@ +/* $OpenBSD: main.c,v 1.77 2009/10/14 17:19:47 sthen Exp $ */ +/* $NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $ */ + +/*- + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * main.c + * Facility: m4 macro processor + * by: oz + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: main.c,v 1.46 2016/01/23 14:24:43 christos Exp $"); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" +#include "pathnames.h" + +ndptr hashtab[HASHSIZE]; /* hash table for macros etc. */ +stae *mstack; /* stack of m4 machine */ +char *sstack; /* shadow stack, for string space extension */ +static size_t STACKMAX; /* current maximum size of stack */ +int sp; /* current m4 stack pointer */ +int fp; /* m4 call frame pointer */ +struct input_file infile[MAXINP];/* input file stack (0=stdin) */ +FILE **outfile; /* diversion array(0=bitbucket)*/ +int maxout; +FILE *active; /* active output file pointer */ +int ilevel = 0; /* input file stack pointer */ +int oindex = 0; /* diversion index.. */ +const char *null = ""; /* as it says.. just a null.. */ +char **m4wraps = NULL; /* m4wraps array. */ +int maxwraps = 0; /* size of m4wraps array */ +int wrapindex = 0; /* current offset in m4wraps */ +char lquote[MAXCCHARS+1] = {LQUOTE}; /* left quote character (`) */ +char rquote[MAXCCHARS+1] = {RQUOTE}; /* right quote character (') */ +char scommt[MAXCCHARS+1] = {SCOMMT}; /* start character for comment */ +char ecommt[MAXCCHARS+1] = {ECOMMT}; /* end character for comment */ +int synch_lines = 0; /* line synchronisation for C preprocessor */ +int prefix_builtins = 0; /* -P option to prefix builtin keywords */ +int fatal_warnings = 0; /* -E option to exit on warnings */ +int quiet = 0; /* -Q option to silence warnings */ +int nesting_limit = -1; /* -L for nesting limit */ +const char *freeze = NULL; /* -F to freeze state */ +const char *reload = NULL; /* -R to reload state */ +#ifndef REAL_FREEZE +FILE *freezef = NULL; +int thawing = 0; +#endif + +struct keyblk { + const char *knam; /* keyword name */ + int ktyp; /* keyword type */ +}; + +struct keyblk keywrds[] = { /* m4 keywords to be installed */ + { "include", INCLTYPE }, + { "sinclude", SINCTYPE }, + { "define", DEFITYPE }, + { "defn", DEFNTYPE }, + { "divert", DIVRTYPE | NOARGS }, + { "expr", EXPRTYPE }, + { "eval", EXPRTYPE }, + { "substr", SUBSTYPE }, + { "ifelse", IFELTYPE }, + { "ifdef", IFDFTYPE }, + { "len", LENGTYPE }, + { "incr", INCRTYPE }, + { "decr", DECRTYPE }, + { "dnl", DNLNTYPE | NOARGS }, + { "changequote", CHNQTYPE | NOARGS }, + { "changecom", CHNCTYPE | NOARGS }, + { "index", INDXTYPE }, +#ifdef EXTENDED + { "paste", PASTTYPE }, + { "spaste", SPASTYPE }, + /* Newer extensions, needed to handle gnu-m4 scripts */ + { "indir", INDIRTYPE}, + { "builtin", BUILTINTYPE}, + { "patsubst", PATSTYPE}, + { "regexp", REGEXPTYPE}, + { "esyscmd", ESYSCMDTYPE}, + { "__file__", FILENAMETYPE | NOARGS}, + { "__line__", LINETYPE | NOARGS}, +#endif + { "popdef", POPDTYPE }, + { "pushdef", PUSDTYPE }, + { "dumpdef", DUMPTYPE | NOARGS }, + { "shift", SHIFTYPE | NOARGS }, + { "translit", TRNLTYPE }, + { "undefine", UNDFTYPE }, + { "undivert", UNDVTYPE | NOARGS }, + { "divnum", DIVNTYPE | NOARGS }, + { "maketemp", MKTMTYPE }, + { "errprint", ERRPTYPE | NOARGS }, + { "m4wrap", M4WRTYPE | NOARGS }, + { "m4exit", EXITTYPE | NOARGS }, + { "syscmd", SYSCTYPE }, + { "sysval", SYSVTYPE | NOARGS }, + { "traceon", TRACEONTYPE | NOARGS }, + { "traceoff", TRACEOFFTYPE | NOARGS }, + +#if defined(unix) || defined(__unix__) + { "unix", SELFTYPE | NOARGS }, +#else +#ifdef vms + { "vms", SELFTYPE | NOARGS }, +#endif +#endif +}; + +#define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk)) + +#define MAXRECORD 50 +static struct position { + char *name; + unsigned long line; +} quotes[MAXRECORD], paren[MAXRECORD]; + +static void record(struct position *, int); +static void dump_stack(struct position *, int); + +static void macro(void); +static void initkwds(void); +static ndptr inspect(int, char *); +static int do_look_ahead(int, const char *); +static void reallyoutputstr(const char *); +static void reallyputchar(int); + +static void enlarge_stack(void); +static void help(void); + +static void +usage(FILE *f) +{ + fprintf(f, "Usage: %s [-EGgiPQsv] [-Dname[=value]] [-d flags] " + "[-I dirname] [-o filename] [-L limit]\n" + "\t[-t macro] [-Uname] [file ...]\n", getprogname()); +} + +__dead static void +onintr(int signo) +{ + char intrmessage[] = "m4: interrupted.\n"; + write(STDERR_FILENO, intrmessage, sizeof(intrmessage)-1); + _exit(1); +} + +#define OPT_HELP 1 + +struct option longopts[] = { + { "debug", optional_argument, 0, 'd' }, + { "define", required_argument, 0, 'D' }, + { "error-output", required_argument, 0, 'e' }, + { "fatal-warnings", no_argument, 0, 'E' }, + { "freeze-state", required_argument, 0, 'F' }, + { "gnu", no_argument, 0, 'g' }, + { "help", no_argument, 0, OPT_HELP }, + { "include", required_argument, 0, 'I' }, + { "interactive", no_argument, 0, 'i' }, + { "nesting-limit", required_argument, 0, 'L' }, + { "prefix-builtins", no_argument, 0, 'P' }, + { "quiet", no_argument, 0, 'Q' }, + { "reload-state", required_argument, 0, 'R' }, + { "silent", no_argument, 0, 'Q' }, + { "synclines", no_argument, 0, 's' }, + { "trace", required_argument, 0, 't' }, + { "traditional", no_argument, 0, 'G' }, + { "undefine", required_argument, 0, 'U' }, + { "version", no_argument, 0, 'v' }, +#ifdef notyet + { "arglength", required_argument, 0, 'l' }, + { "debugfile", optional_argument, 0, OPT_DEBUGFILE }, + { "hashsize", required_argument, 0, 'H' }, + { "warn-macro-sequence",optional_argument, 0, OPT_WARN_SEQUENCE }, +#endif + { 0, 0, 0, 0 }, +}; + +int +main(int argc, char *argv[]) +{ + int c; + int n; + char *p; + + setprogname(argv[0]); + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + signal(SIGINT, onintr); + + init_macros(); + initspaces(); + STACKMAX = INITSTACKMAX; + + mstack = (stae *)xalloc(sizeof(stae) * STACKMAX, NULL); + sstack = (char *)xalloc(STACKMAX, NULL); + + maxout = 0; + outfile = NULL; + resizedivs(MAXOUT); + + while ((c = getopt_long(argc, argv, "D:d:e:EF:GgI:iL:o:PR:Qst:U:v", + longopts, NULL)) != -1) + switch(c) { + case 'D': /* define something..*/ + for (p = optarg; *p; p++) + if (*p == '=') + break; + if (*p) + *p++ = EOS; + dodefine(optarg, p); + break; + case 'd': + set_trace_flags(optarg); + break; + case 'E': + fatal_warnings++; + break; + case 'e': + if (freopen(optarg, "w+", stderr) == NULL) + err(EXIT_FAILURE, "Can't redirect errors to `%s'", + optarg); + break; + case 'F': + freeze = optarg; +#ifndef REAL_FREEZE + if ((freezef = fopen(freeze, "w")) == NULL) + err(EXIT_FAILURE, "Can't open `%s'", freeze); +#endif + break; + case 'I': + addtoincludepath(optarg); + break; + case 'i': + setvbuf(stdout, NULL, _IONBF, 0); + signal(SIGINT, SIG_IGN); + break; + case 'G': + mimic_gnu = 0; + break; + case 'g': + mimic_gnu = 1; + break; + case 'L': + nesting_limit = atoi(optarg); + break; + case 'o': + trace_file(optarg); + break; + case 'P': + prefix_builtins = 1; + break; + case 'Q': + quiet++; + break; + case 'R': + reload = optarg; + break; + case 's': + synch_lines = 1; + break; + case 't': + mark_traced(optarg, 1); + break; + case 'U': /* undefine... */ + macro_popdef(optarg); + break; + case 'v': + fprintf(stderr, "%s version %d\n", getprogname(), + VERSION); + return EXIT_SUCCESS; + case OPT_HELP: + help(); + return EXIT_SUCCESS; + case '?': + default: + usage(stderr); + return EXIT_FAILURE; + } + +#ifdef REDIRECT + /* + * This is meant only for debugging; it makes all output + * go to a known file, even if the command line options + * send it elsewhere. It should not be turned of in production code. + */ + if (freopen("/tmp/m4", "w+", stderr) == NULL) + err(EXIT_FAILURE, "Can't redirect errors to `%s'", + "/tmp/m4"); +#endif + argc -= optind; + argv += optind; + + + initkwds(); + if (mimic_gnu) + setup_builtin("format", FORMATTYPE); + + active = stdout; /* default active output */ + bbase[0] = bufbase; + + if (reload) { +#ifdef REAL_FREEZE + thaw_state(reload); +#else + if (fopen_trypath(infile, reload) == NULL) + err(1, "Can't open `%s'", reload); + sp = -1; + fp = 0; + thawing = 1; + macro(); + thawing = 0; + release_input(infile); +#endif + } + + if (!argc) { + sp = -1; /* stack pointer initialized */ + fp = 0; /* frame pointer initialized */ + set_input(infile+0, stdin, "stdin"); + /* default input (naturally) */ + macro(); + } else + for (; argc--; ++argv) { + p = *argv; + if (p[0] == '-' && p[1] == EOS) + set_input(infile, stdin, "stdin"); + else if (fopen_trypath(infile, p) == NULL) + err(1, "%s", p); + sp = -1; + fp = 0; + macro(); + release_input(infile); + } + + if (wrapindex) { + int i; + + ilevel = 0; /* in case m4wrap includes.. */ + bufbase = bp = buf; /* use the entire buffer */ + if (mimic_gnu) { + while (wrapindex != 0) { + for (i = 0; i < wrapindex; i++) + pbstr(m4wraps[i]); + wrapindex =0; + macro(); + } + } else { + for (i = 0; i < wrapindex; i++) { + pbstr(m4wraps[i]); + macro(); + } + } + } + + if (active != stdout) + active = stdout; /* reset output just in case */ + for (n = 1; n < maxout; n++) /* default wrap-up: undivert */ + if (outfile[n] != NULL) + getdiv(n); + /* remove bitbucket if used */ + if (outfile[0] != NULL) { + (void) fclose(outfile[0]); + } + +#ifdef REAL_FREEZE + if (freeze) + freeze_state(freeze); +#else + if (freezef) + fclose(freezef); +#endif + + return 0; +} + +/* + * Look ahead for `token'. + * (on input `t == token[0]') + * Used for comment and quoting delimiters. + * Returns 1 if `token' present; copied to output. + * 0 if `token' not found; all characters pushed back + */ +static int +do_look_ahead(int t, const char *token) +{ + int i; + + assert((unsigned char)t == (unsigned char)token[0]); + + for (i = 1; *++token; i++) { + t = gpbc(); + if (t == EOF || (unsigned char)t != (unsigned char)*token) { + pushback(t); + while (--i) + pushback(*--token); + return 0; + } + } + return 1; +} + +#define LOOK_AHEAD(t, token) (t != EOF && \ + (unsigned char)(t)==(unsigned char)(token)[0] && \ + do_look_ahead(t,token)) + +/* + * macro - the work horse.. + */ +static void +macro(void) +{ + char token[MAXTOK+1]; + int t, l; + ndptr p; + int nlpar; + + cycle { + t = gpbc(); + + if (LOOK_AHEAD(t,lquote)) { /* strip quotes */ + nlpar = 0; + record(quotes, nlpar++); + /* + * Opening quote: scan forward until matching + * closing quote has been found. + */ + do { + + l = gpbc(); + if (LOOK_AHEAD(l,rquote)) { + if (--nlpar > 0) + outputstr(rquote); + } else if (LOOK_AHEAD(l,lquote)) { + record(quotes, nlpar++); + outputstr(lquote); + } else if (l == EOF) { + if (!quiet) { + if (nlpar == 1) + warnx("unclosed quote:"); + else + warnx( + "%d unclosed quotes:", + nlpar); + dump_stack(quotes, nlpar); + } + exit(EXIT_FAILURE); + } else { + if (nlpar > 0) { + if (sp < 0) + reallyputchar(l); + else + CHRSAVE(l); + } + } + } + while (nlpar != 0); + } else if (sp < 0 && LOOK_AHEAD(t, scommt)) { + reallyoutputstr(scommt); + + for(;;) { + t = gpbc(); + if (LOOK_AHEAD(t, ecommt)) { + reallyoutputstr(ecommt); + break; + } + if (t == EOF) + break; + reallyputchar(t); + } + } else if (t == '_' || isalpha(t)) { + p = inspect(t, token); + if (p != NULL) + pushback(l = gpbc()); + if (p == NULL || (l != LPAREN && + (macro_getdef(p)->type & NEEDARGS) != 0)) + outputstr(token); + else { + /* + * real thing.. First build a call frame: + */ + pushf(fp); /* previous call frm */ + pushf(macro_getdef(p)->type); /* type of the call */ + pushf(is_traced(p)); + pushf(0); /* parenthesis level */ + fp = sp; /* new frame pointer */ + /* + * now push the string arguments: + */ + pushs1(macro_getdef(p)->defn); /* defn string */ + pushs1((char *)macro_name(p)); /* macro name */ + pushs(ep); /* start next..*/ + + if (l != LPAREN && PARLEV == 0) { + /* no bracks */ + chrsave(EOS); + + if ((size_t)sp == STACKMAX) + errx(1, "internal stack overflow"); + eval((const char **) mstack+fp+1, 2, + CALTYP, TRACESTATUS); + + ep = PREVEP; /* flush strspace */ + sp = PREVSP; /* previous sp.. */ + fp = PREVFP; /* rewind stack...*/ + } + } + } else if (t == EOF) { + if (sp > -1 && ilevel <= 0) { + if (!quiet) { + warnx("unexpected end of input, " + "unclosed parenthesis:"); + dump_stack(paren, PARLEV); + } + exit(EXIT_FAILURE); + } + if (ilevel <= 0) + break; /* all done thanks.. */ + release_input(infile+ilevel--); + emit_synchline(); + bufbase = bbase[ilevel]; + continue; + } else if (sp < 0) { /* not in a macro at all */ + reallyputchar(t); /* output directly.. */ + } + + else switch(t) { + + case LPAREN: + if (PARLEV > 0) + chrsave(t); + while (isspace(l = gpbc())) /* skip blank, tab, nl.. */ + if (PARLEV > 0) + chrsave(l); + pushback(l); + record(paren, PARLEV++); + break; + + case RPAREN: + if (--PARLEV > 0) + chrsave(t); + else { /* end of argument list */ + chrsave(EOS); + + if ((size_t)sp == STACKMAX) + errx(1, "internal stack overflow"); + + eval((const char **) mstack+fp+1, sp-fp, + CALTYP, TRACESTATUS); + + ep = PREVEP; /* flush strspace */ + sp = PREVSP; /* previous sp.. */ + fp = PREVFP; /* rewind stack...*/ + } + break; + + case COMMA: + if (PARLEV == 1) { + chrsave(EOS); /* new argument */ + while (isspace(l = gpbc())) + ; + pushback(l); + pushs(ep); + } else + chrsave(t); + break; + + default: + if (LOOK_AHEAD(t, scommt)) { + char *q; + for (q = scommt; *q; q++) + chrsave(*q); + for(;;) { + t = gpbc(); + if (LOOK_AHEAD(t, ecommt)) { + for (q = ecommt; *q; q++) + chrsave(*q); + break; + } + if (t == EOF) + break; + CHRSAVE(t); + } + } else + CHRSAVE(t); /* stack the char */ + break; + } + } +} + +/* + * output string directly, without pushing it for reparses. + */ +void +outputstr(const char *s) +{ + if (sp < 0) + reallyoutputstr(s); + else + while (*s) + CHRSAVE(*s++); +} + +void +reallyoutputstr(const char *s) +{ + if (synch_lines) { + while (*s) { + fputc(*s, active); + if (*s++ == '\n') { + infile[ilevel].synch_lineno++; + if (infile[ilevel].synch_lineno != + infile[ilevel].lineno) + do_emit_synchline(); + } + } + } else + fputs(s, active); +} + +void +reallyputchar(int c) +{ + putc(c, active); + if (synch_lines && c == '\n') { + infile[ilevel].synch_lineno++; + if (infile[ilevel].synch_lineno != infile[ilevel].lineno) + do_emit_synchline(); + } +} + +/* + * build an input token.. + * consider only those starting with _ or A-Za-z. + */ +static ndptr +inspect(int c, char *tp) +{ + char *name = tp; + char *etp = tp+MAXTOK; + ndptr p; + + *tp++ = c; + + while ((isalnum(c = gpbc()) || c == '_') && tp < etp) + *tp++ = c; + if (c != EOF) + PUSHBACK(c); + *tp = EOS; + /* token is too long, it won't match anything, but it can still + * be output. */ + if (tp == ep) { + outputstr(name); + while (isalnum(c = gpbc()) || c == '_') { + if (sp < 0) + reallyputchar(c); + else + CHRSAVE(c); + } + *name = EOS; + return NULL; + } + + p = ohash_find(¯os, ohash_qlookupi(¯os, name, (void *)&tp)); + if (p == NULL) + return NULL; + if (macro_getdef(p) == NULL) + return NULL; + return p; +} + +/* + * initkwds - initialise m4 keywords as fast as possible. + * This very similar to install, but without certain overheads, + * such as calling lookup. Malloc is not used for storing the + * keyword strings, since we simply use the static pointers + * within keywrds block. + */ +static void +initkwds(void) +{ + unsigned int type; + size_t i; + + for (i = 0; i < MAXKEYS; i++) { + type = keywrds[i].ktyp; + if ((keywrds[i].ktyp & NOARGS) == 0) + type |= NEEDARGS; + setup_builtin(keywrds[i].knam, type); + } +} + +static void +record(struct position *t, int lev) +{ + if (lev < MAXRECORD) { + t[lev].name = CURRENT_NAME; + t[lev].line = CURRENT_LINE; + } +} + +static void +dump_stack(struct position *t, int lev) +{ + int i; + + for (i = 0; i < lev; i++) { + if (i == MAXRECORD) { + fprintf(stderr, " ...\n"); + break; + } + fprintf(stderr, " %s at line %lu\n", + t[i].name, t[i].line); + } +} + + +static void +enlarge_stack(void) +{ + STACKMAX += STACKMAX/2; + mstack = xrealloc(mstack, sizeof(stae) * STACKMAX, + "Evaluation stack overflow (%lu)", + (unsigned long)STACKMAX); + sstack = xrealloc(sstack, STACKMAX, + "Evaluation stack overflow (%lu)", + (unsigned long)STACKMAX); +} + +static const struct { + const char *n; + const char *d; +} nd [] = { +{ "-d, --debug[=flags]", "set debug flags" }, +{ "-D, --define=name[=value]", "define macro" }, +{ "-e, --error-output=file", "send error output to file" }, +{ "-E, --fatal-warnings", "exit on warnings" }, +{ "-F, --freeze-state=file", "save state to file" }, +{ "-g, --gnu", "enable gnu extensions" }, +{ " --help", "print this message and exit" }, +{ "-I, --include=file", "include file" }, +{ "-i, --interactive", "unbuffer output, ignore tty signals" }, +{ "-L, --nesting-limit=num", "macro expansion nesting limit (unimpl)" }, +{ "-P, --prefix-builtins", "prefix builtins with m4_" }, +{ "-Q, --quiet", "don't print warnings" }, +{ "-R, --reload-state=file", "restore state from file" }, +{ "-Q, --silent", "don't print warnings" }, +{ "-s, --synclines", "output line directives for cpp(1)" }, +{ "-t, --trace=macro", "trace the named macro" }, +{ "-G, --traditional", "disable gnu extensions" }, +{ "-U, --undefine=name", "undefine the named macro" }, +{ "-v, --version", "print the version number and exit" }, +}; + +static void +help(void) +{ + size_t i; + fprintf(stdout, "%s version %d\n\n", getprogname(), VERSION); + usage(stdout); + + fprintf(stdout, "\nThe long options are:\n"); + for (i = 0; i < __arraycount(nd); i++) + fprintf(stdout, "\t%-25.25s\t%s\n", nd[i].n, nd[i].d); +} diff --git a/usr.bin/m4/mdef.h b/usr.bin/m4/mdef.h new file mode 100644 index 0000000..f80c208 --- /dev/null +++ b/usr.bin/m4/mdef.h @@ -0,0 +1,237 @@ +/* $OpenBSD: mdef.h,v 1.29 2006/03/20 20:27:45 espie Exp $ */ +/* $NetBSD: mdef.h,v 1.17 2016/01/16 18:31:29 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)mdef.h 8.1 (Berkeley) 6/6/93 + */ + +#ifdef __GNUC__ +# define UNUSED __attribute__((__unused__)) +#else +# define UNUSED +#endif + +#define MACRTYPE 1 +#define DEFITYPE 2 +#define EXPRTYPE 3 +#define SUBSTYPE 4 +#define IFELTYPE 5 +#define LENGTYPE 6 +#define CHNQTYPE 7 +#define SYSCTYPE 8 +#define UNDFTYPE 9 +#define INCLTYPE 10 +#define SINCTYPE 11 +#define PASTTYPE 12 +#define SPASTYPE 13 +#define INCRTYPE 14 +#define IFDFTYPE 15 +#define PUSDTYPE 16 +#define POPDTYPE 17 +#define SHIFTYPE 18 +#define DECRTYPE 19 +#define DIVRTYPE 20 +#define UNDVTYPE 21 +#define DIVNTYPE 22 +#define MKTMTYPE 23 +#define ERRPTYPE 24 +#define M4WRTYPE 25 +#define TRNLTYPE 26 +#define DNLNTYPE 27 +#define DUMPTYPE 28 +#define CHNCTYPE 29 +#define INDXTYPE 30 +#define SYSVTYPE 31 +#define EXITTYPE 32 +#define DEFNTYPE 33 +#define SELFTYPE 34 +#define INDIRTYPE 35 +#define BUILTINTYPE 36 +#define PATSTYPE 37 +#define FILENAMETYPE 38 +#define LINETYPE 39 +#define REGEXPTYPE 40 +#define ESYSCMDTYPE 41 +#define TRACEONTYPE 42 +#define TRACEOFFTYPE 43 +#define FORMATTYPE 44 + +#define BUILTIN_MARKER "__builtin_" + +#define TYPEMASK 0xff /* Keep bits really corresponding to a type. */ +#define RECDEF 0x100 /* Pure recursive def, don't expand it */ +#define NOARGS 0x200 /* builtin needs no args */ +#define NEEDARGS 0x400 /* mark builtin that need args with this */ + +/* + * m4 special characters + */ + +#define ARGFLAG '$' +#define LPAREN '(' +#define RPAREN ')' +#define LQUOTE '`' +#define RQUOTE '\'' +#define COMMA ',' +#define SCOMMT '#' +#define ECOMMT '\n' + +#ifdef msdos +#define system(str) (-1) +#endif + +/* + * other important constants + */ + +#define EOS '\0' +#define MAXINP 10 /* maximum include files */ +#define MAXOUT 10 /* maximum # of diversions */ +#define BUFSIZE 4096 /* starting size of pushback buffer */ +#define INITSTACKMAX 4096 /* starting size of call stack */ +#define STRSPMAX 4096 /* starting size of string space */ +#define MAXTOK 512 /* maximum chars in a tokn */ +#define HASHSIZE 199 /* maximum size of hashtab */ +#define MAXCCHARS 5 /* max size of comment/quote delim */ + +#define ALL 1 +#define TOP 0 + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#define cycle for(;;) + +/* + * m4 data structures + */ + +typedef struct ndblock *ndptr; + +struct macro_definition { + struct macro_definition *next; + char *defn; /* definition.. */ + unsigned int type; /* type of the entry.. */ +}; + + +struct ndblock { /* hashtable structure */ + unsigned int builtin_type; + unsigned int trace_flags; + struct macro_definition *d; + char name[1]; /* entry name.. */ +}; + +typedef union { /* stack structure */ + int sfra; /* frame entry */ + char *sstr; /* string entry */ +} stae; + +struct input_file { + FILE *file; + char *name; + unsigned long lineno; + unsigned long synch_lineno; /* used for -s */ + int c; +}; + +#define CURRENT_NAME (infile[ilevel].name) +#define CURRENT_LINE (infile[ilevel].lineno) +#define TOKEN_LINE(f) (f->lineno - (f->c == '\n' ? 1 : 0)) + +/* + * macros for readibility and/or speed + * + * gpbc() - get a possibly pushed-back character + * pushf() - push a call frame entry onto stack + * pushs() - push a string pointer onto stack + */ +#define gpbc() (bp > bufbase) ? *--bp : obtain_char(infile+ilevel) +#define pushf(x) \ + do { \ + if ((size_t)++sp == STACKMAX) \ + enlarge_stack();\ + mstack[sp].sfra = (x); \ + sstack[sp] = 0; \ + } while (0) + +#define pushs(x) \ + do { \ + if ((size_t)++sp == STACKMAX) \ + enlarge_stack();\ + mstack[sp].sstr = (x); \ + sstack[sp] = 1; \ + } while (0) + +#define pushs1(x) \ + do { \ + if ((size_t)++sp == STACKMAX) \ + enlarge_stack();\ + mstack[sp].sstr = (x); \ + sstack[sp] = 0; \ + } while (0) + +/* + * . . + * | . | <-- sp | . | + * +-------+ +-----+ + * | arg 3 ----------------------->| str | + * +-------+ | . | + * | arg 2 ---PREVEP-----+ . + * +-------+ | + * . | | | + * +-------+ | +-----+ + * | plev | PARLEV +-------->| str | + * +-------+ | . | + * | type | CALTYP . + * +-------+ + * | prcf ---PREVFP--+ + * +-------+ | + * | . | PREVSP | + * . | + * +-------+ | + * | <----------+ + * +-------+ + * + */ +#define PARLEV (mstack[fp].sfra) +#define CALTYP (mstack[fp-2].sfra) +#define TRACESTATUS (mstack[fp-1].sfra) +#define PREVEP (mstack[fp+3].sstr) +#define PREVSP (fp-4) +#define PREVFP (mstack[fp-3].sfra) + +#define VERSION 20150116 diff --git a/usr.bin/m4/misc.c b/usr.bin/m4/misc.c new file mode 100644 index 0000000..d7bc8cf --- /dev/null +++ b/usr.bin/m4/misc.c @@ -0,0 +1,414 @@ +/* $OpenBSD: misc.c,v 1.41 2009/10/14 17:19:47 sthen Exp $ */ +/* $NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: misc.c,v 1.24 2016/01/16 17:01:22 christos Exp $"); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" +#include "pathnames.h" + + +char *ep; /* first free char in strspace */ +static char *strspace; /* string space for evaluation */ +char *endest; /* end of string space */ +static size_t strsize = STRSPMAX; +static size_t bufsize = BUFSIZE; + +unsigned char *buf; /* push-back buffer */ +unsigned char *bufbase; /* the base for current ilevel */ +unsigned char *bbase[MAXINP]; /* the base for each ilevel */ +unsigned char *bp; /* first available character */ +unsigned char *endpbb; /* end of push-back buffer */ + + +/* + * find the index of second str in the first str. + */ +ptrdiff_t +indx(const char *s1, const char *s2) +{ + char *t; + + t = strstr(s1, s2); + if (t == NULL) + return (-1); + else + return (t - s1); +} +/* + * pushback - push character back onto input + */ +void +pushback(int c) +{ + if (c == EOF) + return; + if (bp >= endpbb) + enlarge_bufspace(); + *bp++ = c; +} + +/* + * pbstr - push string back onto input + * pushback is replicated to improve + * performance. + */ +void +pbstr(const char *s) +{ + size_t n; + + n = strlen(s); + while ((size_t)(endpbb - bp) <= n) + enlarge_bufspace(); + while (n > 0) + *bp++ = s[--n]; +} + +/* + * pbnum - convert number to string, push back on input. + */ +void +pbnum(int n) +{ + pbnumbase(n, 10, 0); +} + +void +pbnumbase(int n, int base, int d) +{ + static char digits[36] = "0123456789abcdefghijklmnopqrstuvwxyz"; + int num; + int printed = 0; + + if (base > 36) + m4errx(1, "base %d > 36: not supported.", base); + + if (base < 2) + m4errx(1, "bad base %d for conversion.", base); + + num = (n < 0) ? -n : n; + do { + pushback(digits[num % base]); + printed++; + } + while ((num /= base) > 0); + + if (n < 0) + printed++; + while (printed++ < d) + pushback('0'); + + if (n < 0) + pushback('-'); +} + +/* + * pbunsigned - convert unsigned long to string, push back on input. + */ +void +pbunsigned(unsigned long n) +{ + do { + pushback(n % 10 + '0'); + } + while ((n /= 10) > 0); +} + +void +initspaces(void) +{ + int i; + + strspace = xalloc(strsize+1, NULL); + ep = strspace; + endest = strspace+strsize; + buf = (unsigned char *)xalloc(bufsize, NULL); + bufbase = buf; + bp = buf; + endpbb = buf + bufsize; + for (i = 0; i < MAXINP; i++) + bbase[i] = buf; +} + +void +enlarge_strspace(void) +{ + char *newstrspace; + int i; + + strsize *= 2; + newstrspace = malloc(strsize + 1); + if (!newstrspace) + errx(1, "string space overflow"); + memcpy(newstrspace, strspace, strsize/2); + for (i = 0; i <= sp; i++) + if (sstack[i]) + mstack[i].sstr = (mstack[i].sstr - strspace) + + newstrspace; + ep = (ep-strspace) + newstrspace; + free(strspace); + strspace = newstrspace; + endest = strspace + strsize; +} + +void +enlarge_bufspace(void) +{ + unsigned char *newbuf; + int i; + + bufsize += bufsize/2; + newbuf = xrealloc(buf, bufsize, "too many characters pushed back"); + for (i = 0; i < MAXINP; i++) + bbase[i] = (bbase[i]-buf)+newbuf; + bp = (bp-buf)+newbuf; + bufbase = (bufbase-buf)+newbuf; + buf = newbuf; + endpbb = buf+bufsize; +} + +/* + * chrsave - put single char on string space + */ +void +chrsave(int c) +{ + if (ep >= endest) + enlarge_strspace(); + *ep++ = c; +} + +/* + * read in a diversion file, and dispose it. + */ +void +getdiv(int n) +{ + int c; + + if (active == outfile[n]) + m4errx(1, "undivert: diversion still active."); + rewind(outfile[n]); + while ((c = getc(outfile[n])) != EOF) + putc(c, active); + (void) fclose(outfile[n]); + outfile[n] = NULL; +} + +/* + * killdiv - get rid of the diversion files + */ +void +killdiv(void) +{ + int n; + + for (n = 0; n < maxout; n++) + if (outfile[n] != NULL) { + (void) fclose(outfile[n]); + } +} + +void +m4errx(int exval, const char *fmt, ...) +{ + fprintf(stderr, "%s: ", getprogname()); + fprintf(stderr, "%s at line %lu: ", CURRENT_NAME, CURRENT_LINE); + if (fmt != NULL) { + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + } + fprintf(stderr, "\n"); + exit(exval); +} + +/* + * resizedivs: allocate more diversion files */ +void +resizedivs(int n) +{ + int i; + + outfile = (FILE **)xrealloc(outfile, sizeof(FILE *) * n, + "too many diverts %d", n); + for (i = maxout; i < n; i++) + outfile[i] = NULL; + maxout = n; +} + +void * +xalloc(size_t n, const char *fmt, ...) +{ + void *p = malloc(n); + + if (p == NULL) { + if (fmt == NULL) + err(1, "malloc"); + else { + va_list va; + + va_start(va, fmt); + verr(1, fmt, va); + va_end(va); + } + } + return p; +} + +void * +xrealloc(void *old, size_t n, const char *fmt, ...) +{ + char *p = realloc(old, n); + + if (p == NULL) { + free(old); + if (fmt == NULL) + err(1, "realloc"); + else { + va_list va; + + va_start(va, fmt); + verr(1, fmt, va); + va_end(va); + } + } + return p; +} + +char * +xstrdup(const char *s) +{ + char *p = strdup(s); + if (p == NULL) + err(1, "strdup"); + return p; +} + +int +obtain_char(struct input_file *f) +{ + if (f->c == EOF) + return EOF; + + f->c = fgetc(f->file); +#ifndef REAL_FREEZE + if (freezef) + fputc(f->c, freezef); +#endif + if (f->c == '\n') + f->lineno++; + + return f->c; +} + +void +set_input(struct input_file *f, FILE *real, const char *name) +{ + f->file = real; + f->lineno = 1; + f->c = 0; + f->name = xstrdup(name); + emit_synchline(); +} + +void +do_emit_synchline(void) +{ + fprintf(active, "#line %lu \"%s\"\n", + infile[ilevel].lineno, infile[ilevel].name); + infile[ilevel].synch_lineno = infile[ilevel].lineno; +} + +void +release_input(struct input_file *f) +{ + if (f->file != stdin) + fclose(f->file); + f->c = EOF; + /* + * XXX can't free filename, as there might still be + * error information pointing to it. + */ +} + +void +doprintlineno(struct input_file *f) +{ + pbunsigned(TOKEN_LINE(f)); +} + +void +doprintfilename(struct input_file *f) +{ + pbstr(rquote); + pbstr(f->name); + pbstr(lquote); +} + +/* + * buffer_mark/dump_buffer: allows one to save a mark in a buffer, + * and later dump everything that was added since then to a file. + */ +size_t +buffer_mark(void) +{ + return bp - buf; +} + + +void +dump_buffer(FILE *f, size_t m) +{ + unsigned char *s; + + for (s = bp; (size_t)(s - buf) > m;) + fputc(*--s, f); +} diff --git a/usr.bin/m4/parser.y b/usr.bin/m4/parser.y new file mode 100644 index 0000000..f8256ef --- /dev/null +++ b/usr.bin/m4/parser.y @@ -0,0 +1,86 @@ +%{ +/* $NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $ */ +/* $OpenBSD: parser.y,v 1.6 2008/08/21 21:00:14 espie Exp $ */ +/* + * Copyright (c) 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: parser.y,v 1.3 2015/01/04 18:31:09 joerg Exp $"); +#include +#define YYSTYPE int32_t +extern int32_t end_result; +extern int yylex(void); +extern int yyerror(const char *); +%} +%token NUMBER +%token ERROR +%left LOR +%left LAND +%left '|' +%left '^' +%left '&' +%left EQ NE +%left '<' LE '>' GE +%left LSHIFT RSHIFT +%left '+' '-' +%left '*' '/' '%' +%right UMINUS UPLUS '!' '~' + +%% + +top : expr { end_result = $1; } + ; +expr : expr '+' expr { $$ = $1 + $3; } + | expr '-' expr { $$ = $1 - $3; } + | expr '*' expr { $$ = $1 * $3; } + | expr '/' expr { + if ($3 == 0) { + yyerror("division by zero"); + exit(1); + } + $$ = $1 / $3; + } + | expr '%' expr { + if ($3 == 0) { + yyerror("modulo zero"); + exit(1); + } + $$ = $1 % $3; + } + | expr LSHIFT expr { $$ = $1 << $3; } + | expr RSHIFT expr { $$ = $1 >> $3; } + | expr '<' expr { $$ = $1 < $3; } + | expr '>' expr { $$ = $1 > $3; } + | expr LE expr { $$ = $1 <= $3; } + | expr GE expr { $$ = $1 >= $3; } + | expr EQ expr { $$ = $1 == $3; } + | expr NE expr { $$ = $1 != $3; } + | expr '&' expr { $$ = $1 & $3; } + | expr '^' expr { $$ = $1 ^ $3; } + | expr '|' expr { $$ = $1 | $3; } + | expr LAND expr { $$ = $1 && $3; } + | expr LOR expr { $$ = $1 || $3; } + | '(' expr ')' { $$ = $2; } + | '-' expr %prec UMINUS { $$ = -$2; } + | '+' expr %prec UPLUS { $$ = $2; } + | '!' expr { $$ = !$2; } + | '~' expr { $$ = ~$2; } + | NUMBER + ; +%% + diff --git a/usr.bin/m4/pathnames.h b/usr.bin/m4/pathnames.h new file mode 100644 index 0000000..ef788e0 --- /dev/null +++ b/usr.bin/m4/pathnames.h @@ -0,0 +1,40 @@ +/* $NetBSD: pathnames.h,v 1.15 2011/03/08 23:55:19 riz Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Definitions of diversion files. + */ +#define _PATH_DIVNAME "/tmp/m4.0XXXXXXXXXX" /* unix diversion files */ diff --git a/usr.bin/m4/stdd.h b/usr.bin/m4/stdd.h new file mode 100644 index 0000000..0cbd681 --- /dev/null +++ b/usr.bin/m4/stdd.h @@ -0,0 +1,54 @@ +/* $NetBSD: stdd.h,v 1.4 2003/08/07 11:14:34 agc Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Ozan Yigit at York University. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)stdd.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * standard defines + */ + +#define max(a,b) ((a) > (b)? (a): (b)) +#define min(a,b) ((a) < (b)? (a): (b)) + +#define iswhite(c) ((c) == ' ' || (c) == '\t') + +/* + * STREQ is an optimised strcmp(a,b)==0 + * STREQN is an optimised strncmp(a,b,n)==0; assumes n > 0 + */ +#define STREQ(a, b) ((a)[0] == (b)[0] && strcmp(a, b) == 0) +#define STREQN(a, b, n) ((a)[0] == (b)[0] && strncmp(a, b, n) == 0) + +#define YES 1 +#define NO 0 diff --git a/usr.bin/m4/tokenizer.l b/usr.bin/m4/tokenizer.l new file mode 100644 index 0000000..fc4ebf1 --- /dev/null +++ b/usr.bin/m4/tokenizer.l @@ -0,0 +1,108 @@ +%{ +/* $NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $ */ +/* $OpenBSD: tokenizer.l,v 1.6 2008/08/21 21:00:14 espie Exp $ */ +/* + * Copyright (c) 2004 Marc Espie + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include "parser.h" +__RCSID("$NetBSD: tokenizer.l,v 1.6 2012/03/20 20:34:58 matt Exp $"); +#include +#include +#include +#include + +extern int mimic_gnu; +extern int32_t yylval; +extern int yylex(void); +extern int yywrap(void); + +int32_t number(void); +int32_t parse_radix(void); + +%} + +%option nounput +%option noinput + +delim [ \t\n] +ws {delim}+ +hex 0[xX][0-9a-fA-F]+ +oct 0[0-7]* +dec [1-9][0-9]* +radix 0[rR][0-9]+:[0-9a-zA-Z]+ + +%% +{ws} {/* just skip it */} +{hex}|{oct}|{dec} { yylval = number(); return(NUMBER); } +{radix} { if (mimic_gnu) { + yylval = parse_radix(); return(NUMBER); + } else { + return(ERROR); + } + } +"<=" { return(LE); } +">=" { return(GE); } +"<<" { return(LSHIFT); } +">>" { return(RSHIFT); } +"==" { return(EQ); } +"!=" { return(NE); } +"&&" { return(LAND); } +"||" { return(LOR); } +. { return yytext[0]; } +%% + +int32_t +number(void) +{ + long l; + + errno = 0; + l = strtol(yytext, NULL, 0); + if (((l == LONG_MAX || l == LONG_MIN) && errno == ERANGE) || + l > INT32_MAX || l < INT32_MIN) { + fprintf(stderr, "m4: numeric overflow in expr: %s\n", yytext); + } + return l; +} + +int32_t +parse_radix(void) +{ + long base; + char *next; + long l; + + l = 0; + base = strtol(yytext+2, &next, 0); + if (base > 36 || next == NULL) { + fprintf(stderr, "m4: error in number %s\n", yytext); + } else { + next++; + while (*next != 0) { + if (*next >= '0' && *next <= '9') + l = base * l + *next - '0'; + else if (*next >= 'a' && *next <= 'z') + l = base * l + *next - 'a' + 10; + else if (*next >= 'A' && *next <= 'Z') + l = base * l + *next - 'A' + 10; + next++; + } + } + return l; +} + diff --git a/usr.bin/m4/trace.c b/usr.bin/m4/trace.c new file mode 100644 index 0000000..bb9397f --- /dev/null +++ b/usr.bin/m4/trace.c @@ -0,0 +1,203 @@ +/* $NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $ */ +/* $OpenBSD: trace.c,v 1.15 2006/03/24 08:03:44 espie Exp $ */ +/* + * Copyright (c) 2001 Marc Espie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE OPENBSD PROJECT AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OPENBSD + * PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#include +__RCSID("$NetBSD: trace.c,v 1.8 2012/03/20 20:34:58 matt Exp $"); + +#include +#include +#include +#include +#include +#include +#include "mdef.h" +#include "stdd.h" +#include "extern.h" + +FILE *traceout; + +#define TRACE_ARGS 1 +#define TRACE_EXPANSION 2 +#define TRACE_QUOTE 4 +#define TRACE_FILENAME 8 +#define TRACE_LINENO 16 +#define TRACE_CONT 32 +#define TRACE_ID 64 +#define TRACE_NEWFILE 128 /* not implemented yet */ +#define TRACE_INPUT 256 /* not implemented yet */ + +static unsigned int letter_to_flag(int); +static void print_header(struct input_file *); +static int frame_level(void); + + +unsigned int trace_flags = TRACE_QUOTE | TRACE_EXPANSION; + +void +trace_file(const char *name) +{ + + if (traceout && traceout != stderr) + fclose(traceout); + traceout = fopen(name, "w"); + if (!traceout) + err(1, "can't open %s", name); +} + +static unsigned int +letter_to_flag(int c) +{ + switch(c) { + case 'a': + return TRACE_ARGS; + case 'e': + return TRACE_EXPANSION; + case 'q': + return TRACE_QUOTE; + case 'c': + return TRACE_CONT; + case 'x': + return TRACE_ID; + case 'f': + return TRACE_FILENAME; + case 'l': + return TRACE_LINENO; + case 'p': + return TRACE_NEWFILE; + case 'i': + return TRACE_INPUT; + case 't': + return TRACE_ALL; + case 'V': + return ~0; + default: + return 0; + } +} + +void +set_trace_flags(const char *s) +{ + char mode = 0; + unsigned int f = 0; + + if (*s == '+' || *s == '-') + mode = *s++; + while (*s) + f |= letter_to_flag(*s++); + switch(mode) { + case 0: + trace_flags = f; + break; + case '+': + trace_flags |= f; + break; + case '-': + trace_flags &= ~f; + break; + } +} + +static int +frame_level(void) +{ + int level; + int framep; + + for (framep = fp, level = 0; framep != 0; + level++,framep = mstack[framep-3].sfra) + ; + return level; +} + +static void +print_header(struct input_file *inp) +{ + fprintf(traceout, "m4trace:"); + if (trace_flags & TRACE_FILENAME) + fprintf(traceout, "%s:", inp->name); + if (trace_flags & TRACE_LINENO) + fprintf(traceout, "%lu:", TOKEN_LINE(inp)); + fprintf(traceout, " -%d- ", frame_level()); + if (trace_flags & TRACE_ID) + fprintf(traceout, "id %lu: ", expansion_id); +} + +size_t +trace(const char *argv[], int argc, struct input_file *inp) +{ + if (!traceout) + traceout = stderr; + print_header(inp); + if (trace_flags & TRACE_CONT) { + fprintf(traceout, "%s ...\n", argv[1]); + print_header(inp); + } + fprintf(traceout, "%s", argv[1]); + if ((trace_flags & TRACE_ARGS) && argc > 2) { + char delim[3]; + int i; + + delim[0] = LPAREN; + delim[1] = EOS; + for (i = 2; i < argc; i++) { + fprintf(traceout, "%s%s%s%s", delim, + (trace_flags & TRACE_QUOTE) ? lquote : "", + argv[i], + (trace_flags & TRACE_QUOTE) ? rquote : ""); + delim[0] = COMMA; + delim[1] = ' '; + delim[2] = EOS; + } + fprintf(traceout, "%c", RPAREN); + } + if (trace_flags & TRACE_CONT) { + fprintf(traceout, " -> ???\n"); + print_header(inp); + fprintf(traceout, argc > 2 ? "%s(...)" : "%s", argv[1]); + } + if (trace_flags & TRACE_EXPANSION) + return buffer_mark(); + else { + fprintf(traceout, "\n"); + return SIZE_MAX; + } +} + +void +finish_trace(size_t mark) +{ + fprintf(traceout, " -> "); + if (trace_flags & TRACE_QUOTE) + fprintf(traceout, "%s", lquote); + dump_buffer(traceout, mark); + if (trace_flags & TRACE_QUOTE) + fprintf(traceout, "%s", rquote); + fprintf(traceout, "\n"); +} diff --git a/usr.bin/make/Makefile.boot b/usr.bin/make/Makefile.boot new file mode 100644 index 0000000..f5be09c --- /dev/null +++ b/usr.bin/make/Makefile.boot @@ -0,0 +1,45 @@ +# $NetBSD: Makefile.boot,v 1.21 2014/02/24 07:23:44 skrll Exp $ +# +# a very simple makefile... +# +# You only want to use this if you aren't running NetBSD. +# +# modify MACHINE and MACHINE_ARCH as appropriate for your target architecture +# +CC=gcc -O -g + +.c.o: + ${CC} ${CFLAGS} -c $< -o $@ + +MACHINE=i386 +MACHINE_ARCH=i386 +# tested on HP-UX 10.20 +#MAKE_MACHINE=hppa +#MAKE_MACHINE_ARCH=hppa +CFLAGS= -DTARGET_MACHINE=\"${MACHINE}\" \ + -DTARGET_MACHINE_ARCH=\"${MACHINE_ARCH}\" \ + -DMAKE_MACHINE=\"${MACHINE}\" +LIBS= + +OBJ=arch.o buf.o compat.o cond.o dir.o for.o hash.o job.o main.o make.o \ + make_malloc.o parse.o str.o strlist.o suff.o targ.o trace.o var.o util.o + +LIBOBJ= lst.lib/lstAppend.o lst.lib/lstAtEnd.o lst.lib/lstAtFront.o \ + lst.lib/lstClose.o lst.lib/lstConcat.o lst.lib/lstDatum.o \ + lst.lib/lstDeQueue.o lst.lib/lstDestroy.o lst.lib/lstDupl.o \ + lst.lib/lstEnQueue.o lst.lib/lstFind.o lst.lib/lstFindFrom.o \ + lst.lib/lstFirst.o lst.lib/lstForEach.o lst.lib/lstForEachFrom.o \ + lst.lib/lstInit.o lst.lib/lstInsert.o lst.lib/lstIsAtEnd.o \ + lst.lib/lstIsEmpty.o lst.lib/lstLast.o lst.lib/lstMember.o \ + lst.lib/lstNext.o lst.lib/lstOpen.o lst.lib/lstRemove.o \ + lst.lib/lstReplace.o lst.lib/lstSucc.o lst.lib/lstPrev.o + +bmake: ${OBJ} ${LIBOBJ} +# @echo 'make of make and make.0 started.' + ${CC} ${CFLAGS} ${OBJ} ${LIBOBJ} -o bmake ${LIBS} + @ls -l $@ +# nroff -h -man make.1 > make.0 +# @echo 'make of make and make.0 completed.' + +clean: + rm -f ${OBJ} ${LIBOBJ} ${PORTOBJ} bmake diff --git a/usr.bin/make/PSD.doc/Makefile b/usr.bin/make/PSD.doc/Makefile new file mode 100644 index 0000000..67702b8 --- /dev/null +++ b/usr.bin/make/PSD.doc/Makefile @@ -0,0 +1,10 @@ +# $NetBSD: Makefile,v 1.4 2014/07/05 19:22:43 dholland Exp $ +# @(#)Makefile 8.1 (Berkeley) 8/14/93 + +SECTION=reference/ref1 +ARTICLE=make +SRCS= tutorial.ms +MACROS= -ms +EXTRAHTMLFILES=make1.png make2.png + +.include diff --git a/usr.bin/make/PSD.doc/tutorial.ms b/usr.bin/make/PSD.doc/tutorial.ms new file mode 100644 index 0000000..814a09a --- /dev/null +++ b/usr.bin/make/PSD.doc/tutorial.ms @@ -0,0 +1,3794 @@ +.\" $NetBSD: tutorial.ms,v 1.13 2017/03/01 13:05:11 kre Exp $ +.\" Copyright (c) 1988, 1989, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Adam de Boor. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" Copyright (c) 1988, 1989 by Adam de Boor +.\" Copyright (c) 1989 by Berkeley Softworks +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Adam de Boor. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tutorial.ms 8.1 (Berkeley) 8/18/93 +.\" +.EH 'PSD:12-%''PMake \*- A Tutorial' +.OH 'PMake \*- A Tutorial''PSD:12-%' +.\" Ix is an indexing macro similar to .IX but I've disabled it for now +.\" Since that would require 2 passes and I am not in the mood for that. +.de Ix +.. +.\" Rd is section (region) define and Rm is region mention? Again disable for +.\" now. +.de Rd +.. +.de Rm +.. +.\" xH is a macro to provide numbered headers that are automatically stuffed +.\" into a table-of-contents, properly indented, etc. If the first argument +.\" is numeric, it is taken as the depth for numbering (as for .NH), else +.\" the default (1) is assumed. +.\" +.\" @P The initial paragraph distance. +.\" @Q The piece of section number to increment (or 0 if none given) +.\" @R Section header. +.\" @S Indent for toc entry +.\" @T Argument to NH (can't use @Q b/c giving 0 to NH resets the counter) +.de xH +.NH \\$1 +\\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 +.nr PD .1v +.XS \\n% +.ta 0.6i +\\*(SN \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 +.XE +.nr PD .3v +.. +.\" CW is used to place a string in fixed-width or switch to a +.\" fixed-width font. +.\" C is a typewriter font for a laserwriter. Use something else if +.\" you don't have one... +.de CW +.ie !\\n(.$ .ft C +.el \&\\$3\fC\\$1\fP\\$2 +.. +.\" Anything I put in a display I want to be in fixed-width +.am DS +.CW +.. +.\" The stuff in .No produces a little stop sign in the left margin +.\" that says NOTE in it. Unfortunately, it does cause a break, but +.\" hey. Can't have everything. In case you're wondering how I came +.\" up with such weird commands, they came from running grn on a +.\" gremlin file... +.de No +.br +.ne 0.5i +.ie n \{\ +.nr g3 \w'NOTE ' +.po -\\n(g3u +.br +NOTE +.br +.po +\\n(g3u +.\} +.el \{\ +.po -0.5i +.br +.mk +.nr g3 \\n(.f +.nr g4 \\n(.s +.sp -1 +.\" .st cf +\D't 5u' +.sp -1 +\h'50u' +.sp -1 +\D't 3u' +.sp -1 +.sp 7u +\h'53u' +\d\D'p -0.19i 0.0i 0.0i -0.13i 0.30i 0.0i 0.0i 0.13i' +.sp -1 +.ft R +.ps 6 +.nr g8 \\n(.d +.ds g9 "NOTE +.sp 74u +\h'85u'\v'0.85n'\h-\w\\*(g9u/2u\&\\*(g9 +.sp |\\n(g8u +.sp 166u +\D't 3u' +.br +.po +.rt +.ft \\n(g3 +.ps \\n(g4 +.\} +.. +.de Bp +.ie !\\n(.$ .IP \(bu 2 +.el .IP "\&" 2 +.. +.ie n .po +\w'NOTE 'u +.el .po +.3i +.TL +PMake \*- A Tutorial +.AU +Adam de Boor +.AI +Berkeley Softworks +2150 Shattuck Ave, Penthouse +Berkeley, CA 94704 +adam@bsw.uu.net +\&...!uunet!bsw!adam +.FS +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appears in all copies. +The University of California, Berkeley Softworks, and Adam de Boor make no +representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied warranty. +.FE +.PP +.xH 1 Introduction +.LP +PMake is a program for creating other programs, or anything else you +can think of for it to do. The basic idea behind PMake is that, for +any given system, be it a program or a document or whatever, there +will be some files that depend on the state of other files (on when +they were last modified). PMake takes these dependencies, which you +must specify, and uses them to build whatever it is you want it to +build. +.LP +PMake is almost fully-compatible with Make, with which you may already +be familiar. PMake's most important feature is its ability to run +several different jobs at once, making the creation of systems +considerably faster. It also has a great deal more functionality than +Make. Throughout the text, whenever something is mentioned that is an +important difference between PMake and Make (i.e. something that will +cause a makefile to fail if you don't do something about it), or is +simply important, it will be flagged with a little sign in the left +margin, like this: +.No +.LP +This tutorial is divided into three main sections corresponding to basic, +intermediate and advanced PMake usage. If you already know Make well, +you will only need to skim chapter 2 (there are some aspects of +PMake that I consider basic to its use that didn't exist in Make). +Things in chapter 3 make life much easier, while those in chapter 4 +are strictly for those who know what they are doing. Chapter 5 has +definitions for the jargon I use and chapter 6 contains possible +solutions to the problems presented throughout the tutorial. +.xH 1 The Basics of PMake +.LP +PMake takes as input a file that tells a) which files depend on which +other files to be complete and b) what to do about files that are +``out-of-date.'' This file is known as a ``makefile'' and is usually +.Ix 0 def makefile +kept in the top-most directory of the system to be built. While you +can call the makefile anything you want, PMake will look for +.CW Makefile +and +.CW makefile +(in that order) in the current directory if you don't tell it +otherwise. +.Ix 0 def makefile default +To specify a different makefile, use the +.B \-f +flag (e.g. +.CW "pmake -f program.mk" ''). `` +.Ix 0 ref flags -f +.Ix 0 ref makefile other +.LP +A makefile has four different types of lines in it: +.RS +.IP \(bu 2 +File dependency specifications +.IP \(bu 2 +Creation commands +.IP \(bu 2 +Variable assignments +.IP \(bu 2 +Comments, include statements and conditional directives +.RE +.LP +Any line may be continued over multiple lines by ending it with a +backslash. +.Ix 0 def "continuation line" +The backslash, following newline and any initial whitespace +on the following line are compressed into a single space before the +input line is examined by PMake. +.xH 2 Dependency Lines +.LP +As mentioned in the introduction, in any system, there are +dependencies between the files that make up the system. For instance, +in a program made up of several C source files and one header file, +the C files will need to be re-compiled should the header file be +changed. For a document of several chapters and one macro file, the +chapters will need to be reprocessed if any of the macros changes. +.Ix 0 def "dependency" +These are dependencies and are specified by means of dependency lines in +the makefile. +.LP +.Ix 0 def "dependency line" +On a dependency line, there are targets and sources, separated by a +one- or two-character operator. +The targets ``depend'' on the sources and are usually created from +them. +.Ix 0 def target +.Ix 0 def source +.Ix 0 ref operator +Any number of targets and sources may be specified on a dependency line. +All the targets in the line are made to depend on all the sources. +Targets and sources need not be actual files, but every source must be +either an actual file or another target in the makefile. +If you run out of room, use a backslash at the end of the line to continue onto +the next one. +.LP +Any file may be a target and any file may be a source, but the +relationship between the two (or however many) is determined by the +``operator'' that separates them. +.Ix 0 def operator +Three types of operators exist: one specifies that the datedness of a +target is determined by the state of its sources, while another +specifies other files (the sources) that need to be dealt with before +the target can be re-created. The third operator is very similar to +the first, with the additional condition that the target is +out-of-date if it has no sources. These operations are represented by +the colon, the exclamation point and the double-colon, respectively, and are +mutually exclusive. Their exact semantics are as follows: +.IP ":" +.Ix 0 def operator colon +.Ix 0 def : +If a colon is used, a target on the line is considered to be +``out-of-date'' (and in need of creation) if +.RS +.IP \(bu 2 +any of the sources has been modified more recently than the target, or +.IP \(bu 2 +the target doesn't exist. +.RE +.Ix 0 def out-of-date +.IP "\&" +Under this operation, steps will be taken to re-create the target only +if it is found to be out-of-date by using these two rules. +.IP "!" +.Ix 0 def operator force +.Ix 0 def ! +If an exclamation point is used, the target will always be re-created, +but this will not happen until all of its sources have been examined +and re-created, if necessary. +.IP "::" +.Ix 0 def operator double-colon +.Ix 0 def :: +If a double-colon is used, a target is out-of-date if: +.RS +.IP \(bu 2 +any of the sources has been modified more recently than the target, or +.IP \(bu 2 +the target doesn't exist, or +.IP \(bu 2 +the target has no sources. +.RE +.IP "\&" +If the target is out-of-date according to these rules, it will be re-created. +This operator also does something else to the targets, but I'll go +into that in the next section (``Shell Commands''). +.LP +Enough words, now for an example. Take that C program I mentioned +earlier. Say there are three C files +.CW a.c , ( +.CW b.c +and +.CW c.c ) +each of which +includes the file +.CW defs.h . +The dependencies between the files could then be expressed as follows: +.DS +program : a.o b.o c.o +a.o b.o c.o : defs.h +a.o : a.c +b.o : b.c +c.o : c.c +.DE +.LP +You may be wondering at this point, where +.CW a.o , +.CW b.o +and +.CW c.o +came in and why +.I they +depend on +.CW defs.h +and the C files don't. The reason is quite simple: +.CW program +cannot be made by linking together .c files \*- it must be +made from .o files. Likewise, if you change +.CW defs.h , +it isn't the .c files that need to be re-created, it's the .o files. +If you think of dependencies in these terms \*- which files (targets) +need to be created from which files (sources) \*- you should have no problems. +.LP +An important thing to notice about the above example, is that all the +\&.o files appear as targets on more than one line. This is perfectly +all right: the target is made to depend on all the sources mentioned +on all the dependency lines. E.g. +.CW a.o +depends on both +.CW defs.h +and +.CW a.c . +.Ix 0 ref dependency +.No +.LP +The order of the dependency lines in the makefile is +important: the first target on the first dependency line in the +makefile will be the one that gets made if you don't say otherwise. +That's why +.CW program +comes first in the example makefile, above. +.LP +Both targets and sources may contain the standard C-Shell wildcard +characters +.CW { , ( +.CW } , +.CW * , +.CW ? , +.CW [ , +and +.CW ] ), +but the non-curly-brace ones may only appear in the final component +(the file portion) of the target or source. The characters mean the +following things: +.IP \fB{}\fP +These enclose a comma-separated list of options and cause the pattern +to be expanded once for each element of the list. Each expansion +contains a different element. For example, +.CW src/{whiffle,beep,fish}.c +expands to the three words +.CW src/whiffle.c , +.CW src/beep.c , +and +.CW src/fish.c . +These braces may be nested and, unlike the other wildcard characters, +the resulting words need not be actual files. All other wildcard +characters are expanded using the files that exist when PMake is +started. +.IP \fB*\fP +This matches zero or more characters of any sort. +.CW src/*.c +will expand to the same three words as above as long as +.CW src +contains those three files (and no other files that end in +.CW .c ). +.IP \fB?\fP +Matches any single character. +.IP \fB[]\fP +This is known as a character class and contains either a list of +single characters, or a series of character ranges +.CW a-z , ( +for example means all characters between a and z), or both. It matches +any single character contained in the list. E.g. +.CW [A-Za-z] +will match all letters, while +.CW [0123456789] +will match all numbers. +.xH 2 Shell Commands +.LP +``Isn't that nice,'' you say to yourself, ``but how are files +actually `re-created,' as he likes to spell it?'' +The re-creation is accomplished by commands you place in the makefile. +These commands are passed to the Bourne shell (better known as +``/bin/sh'') to be executed and are +.Ix 0 ref shell +.Ix 0 ref re-creation +.Ix 0 ref update +expected to do what's necessary to update the target file (PMake +doesn't actually check to see if the target was created. It just +assumes it's there). +.Ix 0 ref target +.LP +Shell commands in a makefile look a lot like shell commands you would +type at a terminal, with one important exception: each command in a +makefile +.I must +be preceded by at least one tab. +.LP +Each target has associated with it a shell script made up of +one or more of these shell commands. The creation script for a target +should immediately follow the dependency line for that target. While +any given target may appear on more than one dependency line, only one +of these dependency lines may be followed by a creation script, unless +the `::' operator was used on the dependency line. +.Ix 0 ref operator double-colon +.Ix 0 ref :: +.No +.LP +If the double-colon was used, each dependency line for the target +may be followed by a shell script. That script will only be executed +if the target on the associated dependency line is out-of-date with +respect to the sources on that line, according to the rules I gave +earlier. +I'll give you a good example of this later on. +.LP +To expand on the earlier makefile, you might add commands as follows: +.DS +program : a.o b.o c.o + cc a.o b.o c.o \-o program +a.o b.o c.o : defs.h +a.o : a.c + cc \-c a.c +b.o : b.c + cc \-c b.c +c.o : c.c + cc \-c c.c +.DE +.LP +Something you should remember when writing a makefile is, the +commands will be executed if the +.I target +on the dependency line is out-of-date, not the sources. +.Ix 0 ref target +.Ix 0 ref source +.Ix 0 ref out-of-date +In this example, the command +.CW "cc \-c a.c" '' `` +will be executed if +.CW a.o +is out-of-date. Because of the `:' operator, +.Ix 0 ref : +.Ix 0 ref operator colon +this means that should +.CW a.c +.I or +.CW defs.h +have been modified more recently than +.CW a.o , +the command will be executed +.CW a.o "\&" ( +will be considered out-of-date). +.Ix 0 ref out-of-date +.LP +Remember how I said the only difference between a makefile shell +command and a regular shell command was the leading tab? I lied. There +is another way in which makefile commands differ from regular ones. +The first two characters after the initial whitespace are treated +specially. +If they are any combination of `@' and `\-', they cause PMake to do +different things. +.LP +In most cases, shell commands are printed before they're +actually executed. This is to keep you informed of what's going on. If +an `@' appears, however, this echoing is suppressed. In the case of an +.CW echo +command, say +.CW "echo Linking index" ,'' `` +it would be +rather silly to see +.DS +echo Linking index +Linking index +.DE +.LP +so PMake allows you to place an `@' before the command +.CW "@echo Linking index" '') (`` +to prevent the command from being printed. +.LP +The other special character is the `\-'. In case you didn't know, +shell commands finish with a certain ``exit status.'' This status is +made available by the operating system to whatever program invoked the +command. Normally this status will be 0 if everything went ok and +non-zero if something went wrong. For this reason, PMake will consider +an error to have occurred if one of the shells it invokes returns a non-zero +status. When it detects an error, PMake's usual action is to abort +whatever it's doing and exit with a non-zero status itself (any other +targets that were being created will continue being made, but nothing +new will be started. PMake will exit after the last job finishes). +This behavior can be altered, however, by placing a `\-' at the front +of a command +.CW "\-mv index index.old" ''), (`` +certain command-line arguments, +or doing other things, to be detailed later. In such +a case, the non-zero status is simply ignored and PMake keeps chugging +along. +.No +.LP +Because all the commands are given to a single shell to execute, such +things as setting shell variables, changing directories, etc., last +beyond the command in which they are found. This also allows shell +compound commands (like +.CW for +loops) to be entered in a natural manner. +Since this could cause problems for some makefiles that depend on +each command being executed by a single shell, PMake has a +.B \-B +.Ix 0 ref compatibility +.Ix 0 ref flags -B +flag (it stands for backwards-compatible) that forces each command to +be given to a separate shell. It also does several other things, all +of which I discourage since they are now old-fashioned.\|.\|.\|. +.No +.LP +A target's shell script is fed to the shell on its (the shell's) input stream. +This means that any commands, such as +.CW ci +that need to get input from the terminal won't work right \*- they'll +get the shell's input, something they probably won't find to their +liking. A simple way around this is to give a command like this: +.DS +ci $(SRCS) < /dev/tty +.DE +This would force the program's input to come from the terminal. If you +can't do this for some reason, your only other alternative is to use +PMake in its fullest compatibility mode. See +.B Compatibility +in chapter 4. +.Ix 0 ref compatibility +.LP +.xH 2 Variables +.LP +PMake, like Make before it, has the ability to save text in variables +to be recalled later at your convenience. Variables in PMake are used +much like variables in the shell and, by tradition, consist of +all upper-case letters (you don't +.I have +to use all upper-case letters. +In fact there's nothing to stop you from calling a variable +.CW @^&$%$ . +Just tradition). Variables are assigned-to using lines of the form +.Ix 0 def variable assignment +.DS +VARIABLE = value +.DE +.Ix 0 def variable assignment +appended-to by +.DS +VARIABLE += value +.DE +.Ix 0 def variable appending +.Ix 0 def variable assignment appended +.Ix 0 def += +conditionally assigned-to (if the variable isn't already defined) by +.DS +VARIABLE ?= value +.DE +.Ix 0 def variable assignment conditional +.Ix 0 def ?= +and assigned-to with expansion (i.e. the value is expanded (see below) +before being assigned to the variable\*-useful for placing a value at +the beginning of a variable, or other things) by +.DS +VARIABLE := value +.DE +.Ix 0 def variable assignment expanded +.Ix 0 def := +.LP +Any whitespace before +.I value +is stripped off. When appending, a space is placed between the old +value and the stuff being appended. +.LP +The final way a variable may be assigned to is using +.DS +VARIABLE != shell-command +.DE +.Ix 0 def variable assignment shell-output +.Ix 0 def != +In this case, +.I shell-command +has all its variables expanded (see below) and is passed off to a +shell to execute. The output of the shell is then placed in the +variable. Any newlines (other than the final one) are replaced by +spaces before the assignment is made. This is typically used to find +the current directory via a line like: +.DS +CWD != pwd +.DE +.LP +.B Note: +this is intended to be used to execute commands that produce small amounts +of output (e.g. ``pwd''). The implementation is less than intelligent and will +likely freeze if you execute something that produces thousands of +bytes of output (8 Kb is the limit on many UNIX systems). +.LP +The value of a variable may be retrieved by enclosing the variable +name in parentheses or curly braces and preceding the whole thing +with a dollar sign. +.LP +For example, to set the variable CFLAGS to the string +.CW "\-I/sprite/src/lib/libc \-O" ,'' `` +you would place a line +.DS +CFLAGS = \-I/sprite/src/lib/libc \-O +.DE +in the makefile and use the word +.CW "$(CFLAGS)" +wherever you would like the string +.CW "\-I/sprite/src/lib/libc \-O" +to appear. This is called variable expansion. +.Ix 0 def variable expansion +.No +.LP +Unlike Make, PMake will not expand a variable unless it knows +the variable exists. E.g. if you have a +.CW "${i}" +in a shell command and you have not assigned a value to the variable +.CW i +(the empty string is considered a value, by the way), where Make would have +substituted the empty string, PMake will leave the +.CW "${i}" +alone. +To keep PMake from substituting for a variable it knows, precede the +dollar sign with another dollar sign. +(e.g. to pass +.CW "${HOME}" +to the shell, use +.CW "$${HOME}" ). +This causes PMake, in effect, to expand the +.CW $ +macro, which expands to a single +.CW $ . +For compatibility, Make's style of variable expansion will be used +if you invoke PMake with any of the compatibility flags (\c +.B \-V , +.B \-B +or +.B \-M . +The +.B \-V +flag alters just the variable expansion). +.Ix 0 ref flags -V +.Ix 0 ref flags -B +.Ix 0 ref flags -M +.Ix 0 ref compatibility +.LP +.Ix 0 ref variable expansion +There are two different times at which variable expansion occurs: +When parsing a dependency line, the expansion occurs immediately +upon reading the line. If any variable used on a dependency line is +undefined, PMake will print a message and exit. +Variables in shell commands are expanded when the command is +executed. +Variables used inside another variable are expanded whenever the outer +variable is expanded (the expansion of an inner variable has no effect +on the outer variable. I.e. if the outer variable is used on a dependency +line and in a shell command, and the inner variable changes value +between when the dependency line is read and the shell command is +executed, two different values will be substituted for the outer +variable). +.Ix 0 def variable types +.LP +Variables come in four flavors, though they are all expanded the same +and all look about the same. They are (in order of expanding scope): +.RS +.IP \(bu 2 +Local variables. +.Ix 0 ref variable local +.IP \(bu 2 +Command-line variables. +.Ix 0 ref variable command-line +.IP \(bu 2 +Global variables. +.Ix 0 ref variable global +.IP \(bu 2 +Environment variables. +.Ix 0 ref variable environment +.RE +.LP +The classification of variables doesn't matter much, except that the +classes are searched from the top (local) to the bottom (environment) +when looking up a variable. The first one found wins. +.xH 3 Local Variables +.LP +.Ix 0 def variable local +Each target can have as many as seven local variables. These are +variables that are only ``visible'' within that target's shell script +and contain such things as the target's name, all of its sources (from +all its dependency lines), those sources that were out-of-date, etc. +Four local variables are defined for all targets. They are: +.RS +.IP ".TARGET" +.Ix 0 def variable local .TARGET +.Ix 0 def .TARGET +The name of the target. +.IP ".OODATE" +.Ix 0 def variable local .OODATE +.Ix 0 def .OODATE +The list of the sources for the target that were considered out-of-date. +The order in the list is not guaranteed to be the same as the order in +which the dependencies were given. +.IP ".ALLSRC" +.Ix 0 def variable local .ALLSRC +.Ix 0 def .ALLSRC +The list of all sources for this target in the order in which they +were given. +.IP ".PREFIX" +.Ix 0 def variable local .PREFIX +.Ix 0 def .PREFIX +The target without its suffix and without any leading path. E.g. for +the target +.CW ../../lib/compat/fsRead.c , +this variable would contain +.CW fsRead . +.RE +.LP +Three other local variables are set only for certain targets under +special circumstances. These are the ``.IMPSRC,'' +.Ix 0 ref variable local .IMPSRC +.Ix 0 ref .IMPSRC +``.ARCHIVE,'' +.Ix 0 ref variable local .ARCHIVE +.Ix 0 ref .ARCHIVE +and ``.MEMBER'' +.Ix 0 ref variable local .MEMBER +.Ix 0 ref .MEMBER +variables. When they are set and how they are used is described later. +.LP +Four of these variables may be used in sources as well as in shell +scripts. +.Ix 0 def "dynamic source" +.Ix 0 def source dynamic +These are ``.TARGET'', ``.PREFIX'', ``.ARCHIVE'' and ``.MEMBER''. The +variables in the sources are expanded once for each target on the +dependency line, providing what is known as a ``dynamic source,'' +.Rd 0 +allowing you to specify several dependency lines at once. For example, +.DS +$(OBJS) : $(.PREFIX).c +.DE +will create a dependency between each object file and its +corresponding C source file. +.xH 3 Command-line Variables +.LP +.Ix 0 def variable command-line +Command-line variables are set when PMake is first invoked by giving a +variable assignment as one of the arguments. For example, +.DS +pmake "CFLAGS = -I/sprite/src/lib/libc -O" +.DE +would make +.CW CFLAGS +be a command-line variable with the given value. Any assignments to +.CW CFLAGS +in the makefile will have no effect, because once it +is set, there is (almost) nothing you can do to change a command-line +variable (the search order, you see). Command-line variables may be +set using any of the four assignment operators, though only +.CW = +and +.CW ?= +behave as you would expect them to, mostly because assignments to +command-line variables are performed before the makefile is read, thus +the values set in the makefile are unavailable at the time. +.CW += +.Ix 0 ref += +.Ix 0 ref variable assignment appended +is the same as +.CW = , +because the old value of the variable is sought only in the scope in +which the assignment is taking place (for reasons of efficiency that I +won't get into here). +.CW := +and +.CW ?= +.Ix 0 ref := +.Ix 0 ref ?= +.Ix 0 ref variable assignment expanded +.Ix 0 ref variable assignment conditional +will work if the only variables used are in the environment. +.CW != +is sort of pointless to use from the command line, since the same +effect can no doubt be accomplished using the shell's own command +substitution mechanisms (backquotes and all that). +.xH 3 Global Variables +.LP +.Ix 0 def variable global +Global variables are those set or appended-to in the makefile. +There are two classes of global variables: those you set and those PMake sets. +As I said before, the ones you set can have any name you want them to have, +except they may not contain a colon or an exclamation point. +The variables PMake sets (almost) always begin with a +period and always contain upper-case letters, only. The variables are +as follows: +.RS +.IP .PMAKE +.Ix 0 def variable global .PMAKE +.Ix 0 def .PMAKE +.Ix 0 def variable global MAKE +.Ix 0 def MAKE +The name by which PMake was invoked is stored in this variable. For +compatibility, the name is also stored in the MAKE variable. +.IP .MAKEFLAGS +.Ix 0 def variable global .MAKEFLAGS +.Ix 0 def .MAKEFLAGS variable +.Ix 0 def variable global MFLAGS +.Ix 0 def MFLAGS +All the relevant flags with which PMake was invoked. This does not +include such things as +.B \-f +or variable assignments. Again for compatibility, this value is stored +in the MFLAGS variable as well. +.RE +.LP +Two other variables, ``.INCLUDES'' and ``.LIBS,'' are covered in the +section on special targets in chapter 3. +.Ix 0 ref variable global .INCLUDES +.Ix 0 ref variable global .LIBS +.LP +Global variables may be deleted using lines of the form: +.Ix 0 def #undef +.Ix 0 def variable deletion +.DS +#undef \fIvariable\fP +.DE +The +.CW # ' ` +must be the first character on the line. Note that this may only be +done on global variables. +.xH 3 Environment Variables +.LP +.Ix 0 def variable environment +Environment variables are passed by the shell that invoked PMake and +are given by PMake to each shell it invokes. They are expanded like +any other variable, but they cannot be altered in any way. +.LP +One special environment variable, +.CW PMAKE , +.Ix 0 def variable environment PMAKE +is examined by PMake for command-line flags, variable assignments, +etc., it should always use. This variable is examined before the +actual arguments to PMake are. In addition, all flags given to PMake, +either through the +.CW PMAKE +variable or on the command line, are placed in this environment +variable and exported to each shell PMake executes. Thus recursive +invocations of PMake automatically receive the same flags as the +top-most one. +.LP +Using all these variables, you can compress the sample makefile even more: +.DS +OBJS = a.o b.o c.o +program : $(OBJS) + cc $(.ALLSRC) \-o $(.TARGET) +$(OBJS) : defs.h +a.o : a.c + cc \-c a.c +b.o : b.c + cc \-c b.c +c.o : c.c + cc \-c c.c +.DE +.Ix 0 ref variable local .ALLSRC +.Ix 0 ref .ALLSRC +.Ix 0 ref variable local .TARGET +.Ix 0 ref .TARGET +.Rd 3 +.xH 2 Comments +.LP +.Ix 0 def comments +Comments in a makefile start with a `#' character and extend to the +end of the line. They may appear +anywhere you want them, except in a shell command (though the shell +will treat it as a comment, too). If, for some reason, you need to use the `#' +in a variable or on a dependency line, put a backslash in front of it. +PMake will compress the two into a single `#' (Note: this isn't true +if PMake is operating in full-compatibility mode). +.Ix 0 ref flags -M +.Ix 0 ref compatibility +.xH 2 Parallelism +.No +.LP +PMake was specifically designed to re-create several targets at once, +when possible. You do not have to do anything special to cause this to +happen (unless PMake was configured to not act in parallel, in which +case you will have to make use of the +.B \-L +and +.B \-J +flags (see below)), +.Ix 0 ref flags -L +.Ix 0 ref flags -J +but you do have to be careful at times. +.LP +There are several problems you are likely to encounter. One is +that some makefiles (and programs) are written in such a way that it is +impossible for two targets to be made at once. The program +.CW xstr , +for example, +always modifies the files +.CW strings +and +.CW x.c . +There is no way to change it. Thus you cannot run two of them at once +without something being trashed. Similarly, if you have commands +in the makefile that always send output to the same file, you will not +be able to make more than one target at once unless you change the +file you use. You can, for instance, add a +.CW $$$$ +to the end of the file name to tack on the process ID of the shell +executing the command (each +.CW $$ +expands to a single +.CW $ , +thus giving you the shell variable +.CW $$ ). +Since only one shell is used for all the +commands, you'll get the same file name for each command in the +script. +.LP +The other problem comes from improperly-specified dependencies that +worked in Make because of its sequential, depth-first way of examining +them. While I don't want to go into depth on how PMake +works (look in chapter 4 if you're interested), I will warn you that +files in two different ``levels'' of the dependency tree may be +examined in a different order in PMake than they were in Make. For +example, given the makefile +.DS +a : b c +b : d +.DE +PMake will examine the targets in the order +.CW c , +.CW d , +.CW b , +.CW a . +If the makefile's author expected PMake to abort before making +.CW c +if an error occurred while making +.CW b , +or if +.CW b +needed to exist before +.CW c +was made, +s/he will be sorely disappointed. The dependencies are +incomplete, since in both these cases, +.CW c +would depend on +.CW b . +So watch out. +.LP +Another problem you may face is that, while PMake is set up to handle the +output from multiple jobs in a graceful fashion, the same is not so for input. +It has no way to regulate input to different jobs, +so if you use the redirection from +.CW /dev/tty +I mentioned earlier, you must be careful not to run two of the jobs at once. +.xH 2 Writing and Debugging a Makefile +.LP +Now you know most of what's in a makefile, what do you do next? There +are two choices: (1) use one of the uncommonly-available makefile +generators or (2) write your own makefile (I leave out the third choice of +ignoring PMake and doing everything by hand as being beyond the bounds +of common sense). +.LP +When faced with the writing of a makefile, it is usually best to start +from first principles: just what +.I are +you trying to do? What do you want the makefile finally to produce? +.LP +To begin with a somewhat traditional example, let's say you need to +write a makefile to create a program, +.CW expr , +that takes standard infix expressions and converts them to prefix form (for +no readily apparent reason). You've got three source files, in C, that +make up the program: +.CW main.c , +.CW parse.c , +and +.CW output.c . +Harking back to my pithy advice about dependency lines, you write the +first line of the file: +.DS +expr : main.o parse.o output.o +.DE +because you remember +.CW expr +is made from +.CW .o +files, not +.CW .c +files. Similarly for the +.CW .o +files you produce the lines: +.DS +main.o : main.c +parse.o : parse.c +output.o : output.c +main.o parse.o output.o : defs.h +.DE +.LP +Great. You've now got the dependencies specified. What you need now is +commands. These commands, remember, must produce the target on the +dependency line, usually by using the sources you've listed. +You remember about local variables? Good, so it should come +to you as no surprise when you write +.DS +expr : main.o parse.o output.o + cc -o $(.TARGET) $(.ALLSRC) +.DE +Why use the variables? If your program grows to produce postfix +expressions too (which, of course, requires a name change or two), it +is one fewer place you have to change the file. You cannot do this for +the object files, however, because they depend on their corresponding +source files +.I and +.CW defs.h , +thus if you said +.DS + cc -c $(.ALLSRC) +.DE +you'd get (for +.CW main.o ): +.DS + cc -c main.c defs.h +.DE +which is wrong. So you round out the makefile with these lines: +.DS +main.o : main.c + cc -c main.c +parse.o : parse.c + cc -c parse.c +output.o : output.c + cc -c output.c +.DE +.LP +The makefile is now complete and will, in fact, create the program you +want it to without unnecessary compilations or excessive typing on +your part. There are two things wrong with it, however (aside from it +being altogether too long, something I'll address in chapter 3): +.IP 1) +The string +.CW "main.o parse.o output.o" '' `` +is repeated twice, necessitating two changes when you add postfix +(you were planning on that, weren't you?). This is in direct violation +of de Boor's First Rule of writing makefiles: +.QP +.I +Anything that needs to be written more than once +should be placed in a variable. +.IP "\&" +I cannot emphasize this enough as being very important to the +maintenance of a makefile and its program. +.IP 2) +There is no way to alter the way compilations are performed short of +editing the makefile and making the change in all places. This is evil +and violates de Boor's Second Rule, which follows directly from the +first: +.QP +.I +Any flags or programs used inside a makefile should be placed in a variable so +they may be changed, temporarily or permanently, with the greatest ease. +.LP +The makefile should more properly read: +.DS +OBJS = main.o parse.o output.o +expr : $(OBJS) + $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) +main.o : main.c + $(CC) $(CFLAGS) -c main.c +parse.o : parse.c + $(CC) $(CFLAGS) -c parse.c +output.o : output.c + $(CC) $(CFLAGS) -c output.c +$(OBJS) : defs.h +.DE +Alternatively, if you like the idea of dynamic sources mentioned in +section 2.3.1, +.Rm 0 2.3.1 +.Rd 4 +.Ix 0 ref "dynamic source" +.Ix 0 ref source dynamic +you could write it like this: +.DS +OBJS = main.o parse.o output.o +expr : $(OBJS) + $(CC) $(CFLAGS) -o $(.TARGET) $(.ALLSRC) +$(OBJS) : $(.PREFIX).c defs.h + $(CC) $(CFLAGS) -c $(.PREFIX).c +.DE +These two rules and examples lead to de Boor's First Corollary: +.QP +.I +Variables are your friends. +.LP +Once you've written the makefile comes the sometimes-difficult task of +.Ix 0 ref debugging +making sure the darn thing works. Your most helpful tool to make sure +the makefile is at least syntactically correct is the +.B \-n +.Ix 0 ref flags -n +flag, which allows you to see if PMake will choke on the makefile. The +second thing the +.B \-n +flag lets you do is see what PMake would do without it actually doing +it, thus you can make sure the right commands would be executed were +you to give PMake its head. +.LP +When you find your makefile isn't behaving as you hoped, the first +question that comes to mind (after ``What time is it, anyway?'') is +``Why not?'' In answering this, two flags will serve you well: +.CW "-d m" '' `` +.Ix 0 ref flags -d +and +.CW "-p 2" .'' `` +.Ix 0 ref flags -p +The first causes PMake to tell you as it examines each target in the +makefile and indicate why it is deciding whatever it is deciding. You +can then use the information printed for other targets to see where +you went wrong. The +.CW "-p 2" '' `` +flag makes PMake print out its internal state when it is done, +allowing you to see that you forgot to make that one chapter depend on +that file of macros you just got a new version of. The output from +.CW "-p 2" '' `` +is intended to resemble closely a real makefile, but with additional +information provided and with variables expanded in those commands +PMake actually printed or executed. +.LP +Something to be especially careful about is circular dependencies. +.Ix 0 def dependency circular +E.g. +.DS +a : b +b : c d +d : a +.DE +In this case, because of how PMake works, +.CW c +is the only thing PMake will examine, because +.CW d +and +.CW a +will effectively fall off the edge of the universe, making it +impossible to examine +.CW b +(or them, for that matter). +PMake will tell you (if run in its normal mode) all the targets +involved in any cycle it looked at (i.e. if you have two cycles in the +graph (naughty, naughty), but only try to make a target in one of +them, PMake will only tell you about that one. You'll have to try to +make the other to find the second cycle). When run as Make, it will +only print the first target in the cycle. +.xH 2 Invoking PMake +.LP +.Ix 0 ref flags +.Ix 0 ref arguments +.Ix 0 ref usage +PMake comes with a wide variety of flags to choose from. +They may appear in any order, interspersed with command-line variable +assignments and targets to create. +The flags are as follows: +.IP "\fB\-d\fP \fIwhat\fP" +.Ix 0 def flags -d +.Ix 0 ref debugging +This causes PMake to spew out debugging information that +may prove useful to you. If you can't +figure out why PMake is doing what it's doing, you might try using +this flag. The +.I what +parameter is a string of single characters that tell PMake what +aspects you are interested in. Most of what I describe will make +little sense to you, unless you've dealt with Make before. Just +remember where this table is and come back to it as you read on. +The characters and the information they produce are as follows: +.RS +.IP a +Archive searching and caching. +.IP c +Conditional evaluation. +.IP d +The searching and caching of directories. +.IP j +Various snippets of information related to the running of the multiple +shells. Not particularly interesting. +.IP m +The making of each target: what target is being examined; when it was +last modified; whether it is out-of-date; etc. +.IP p +Makefile parsing. +.IP r +Remote execution. +.IP s +The application of suffix-transformation rules. (See chapter 3) +.IP t +The maintenance of the list of targets. +.IP v +Variable assignment. +.RE +.IP "\&" +Of these all, the +.CW m +and +.CW s +letters will be most useful to you. +If the +.B \-d +is the final argument or the argument from which it would get these +key letters (see below for a note about which argument would be used) +begins with a +.B \- , +all of these debugging flags will be set, resulting in massive amounts +of output. +.IP "\fB\-f\fP \fImakefile\fP" +.Ix 0 def flags -f +Specify a makefile to read different from the standard makefiles +.CW Makefile "\&" ( +or +.CW makefile ). +.Ix 0 ref makefile default +.Ix 0 ref makefile other +If +.I makefile +is ``\-'', PMake uses the standard input. This is useful for making +quick and dirty makefiles.\|.\|. +.Ix 0 ref makefile "quick and dirty" +.IP \fB\-h\fP +.Ix 0 def flags -h +Prints out a summary of the various flags PMake accepts. It can also +be used to find out what level of concurrency was compiled into the +version of PMake you are using (look at +.B \-J +and +.B \-L ) +and various other information on how PMake was configured. +.Ix 0 ref configuration +.Ix 0 ref makefile system +.IP \fB\-i\fP +.Ix 0 def flags -i +If you give this flag, PMake will ignore non-zero status returned +by any of its shells. It's like placing a `\-' before all the commands +in the makefile. +.IP \fB\-k\fP +.Ix 0 def flags -k +This is similar to +.B \-i +in that it allows PMake to continue when it sees an error, but unlike +.B \-i , +where PMake continues blithely as if nothing went wrong, +.B \-k +causes it to recognize the error and only continue work on those +things that don't depend on the target, either directly or indirectly (through +depending on something that depends on it), whose creation returned the error. +The `k' is for ``keep going''.\|.\|. +.Ix 0 ref target +.IP \fB\-l\fP +.Ix 0 def flags -l +PMake has the ability to lock a directory against other +people executing it in the same directory (by means of a file called +``LOCK.make'' that it creates and checks for in the directory). This +is a Good Thing because two people doing the same thing in the same place +can be disastrous for the final product (too many cooks and all that). +Whether this locking is the default is up to your system +administrator. If locking is on, +.B \-l +will turn it off, and vice versa. Note that this locking will not +prevent \fIyou\fP from invoking PMake twice in the same place \*- if +you own the lock file, PMake will warn you about it but continue to execute. +.IP "\fB\-m\fP \fIdirectory\fP" +.Ix 0 def flags -m +Tells PMake another place to search for included makefiles via the <...> +style. Several +.B \-m +options can be given to form a search path. If this construct is used the +default system makefile search path is completely overridden. +To be explained in chapter 3, section 3.2. +.Rm 2 3.2 +.IP \fB\-n\fP +.Ix 0 def flags -n +This flag tells PMake not to execute the commands needed to update the +out-of-date targets in the makefile. Rather, PMake will simply print +the commands it would have executed and exit. This is particularly +useful for checking the correctness of a makefile. If PMake doesn't do +what you expect it to, it's a good chance the makefile is wrong. +.IP "\fB\-p\fP \fInumber\fP" +.Ix 0 def flags -p +.Ix 0 ref debugging +This causes PMake to print its input in a reasonable form, though +not necessarily one that would make immediate sense to anyone but me. The +.I number +is a bitwise-or of 1 and 2 where 1 means it should print the input +before doing any processing and 2 says it should print it after +everything has been re-created. Thus +.CW "\-p 3" +would print it twice\*-once before processing and once after (you +might find the difference between the two interesting). This is mostly +useful to me, but you may find it informative in some bizarre circumstances. +.IP \fB\-q\fP +.Ix 0 def flags -q +If you give PMake this flag, it will not try to re-create anything. It +will just see if anything is out-of-date and exit non-zero if so. +.IP \fB\-r\fP +.Ix 0 def flags -r +When PMake starts up, it reads a default makefile that tells it what +sort of system it's on and gives it some idea of what to do if you +don't tell it anything. I'll tell you about it in chapter 3. If you +give this flag, PMake won't read the default makefile. +.IP \fB\-s\fP +.Ix 0 def flags -s +This causes PMake to not print commands before they're executed. It +is the equivalent of putting an `@' before every command in the +makefile. +.IP \fB\-t\fP +.Ix 0 def flags -t +Rather than try to re-create a target, PMake will simply ``touch'' it +so as to make it appear up-to-date. If the target didn't exist before, +it will when PMake finishes, but if the target did exist, it will +appear to have been updated. +.IP \fB\-v\fP +.Ix 0 def flags -v +This is a mixed-compatibility flag intended to mimic the System V +version of Make. It is the same as giving +.B \-B , +and +.B \-V +as well as turning off directory locking. Targets can still be created +in parallel, however. This is the mode PMake will enter if it is +invoked either as +.CW smake '' `` +or +.CW vmake ''. `` +.IP \fB\-x\fP +.Ix 0 def flags -x +This tells PMake it's ok to export jobs to other machines, if they're +available. It is used when running in Make mode, as exporting in this +mode tends to make things run slower than if the commands were just +executed locally. +.IP \fB\-B\fP +.Ix 0 ref compatibility +.Ix 0 def flags -B +Forces PMake to be as backwards-compatible with Make as possible while +still being itself. +This includes: +.RS +.IP \(bu 2 +Executing one shell per shell command +.IP \(bu 2 +Expanding anything that looks even vaguely like a variable, with the +empty string replacing any variable PMake doesn't know. +.IP \(bu 2 +Refusing to allow you to escape a `#' with a backslash. +.IP \(bu 2 +Permitting undefined variables on dependency lines and conditionals +(see below). Normally this causes PMake to abort. +.RE +.IP \fB\-C\fP +.Ix 0 def flags -C +This nullifies any and all compatibility mode flags you may have given +or implied up to the time the +.B \-C +is encountered. It is useful mostly in a makefile that you wrote for PMake +to avoid bad things happening when someone runs PMake as +.CW make '' `` +or has things set in the environment that tell it to be compatible. +.B \-C +is +.I not +placed in the +.CW PMAKE +environment variable or the +.CW .MAKEFLAGS +or +.CW MFLAGS +global variables. +.Ix 0 ref variable environment PMAKE +.Ix 0 ref variable global .MAKEFLAGS +.Ix 0 ref variable global MFLAGS +.Ix 0 ref .MAKEFLAGS variable +.Ix 0 ref MFLAGS +.IP "\fB\-D\fP \fIvariable\fP" +.Ix 0 def flags -D +Allows you to define a variable to have +.CW 1 '' `` +as its value. The variable is a global variable, not a command-line +variable. This is useful mostly for people who are used to the C +compiler arguments and those using conditionals, which I'll get into +in section 4.3 +.Rm 1 4.3 +.IP "\fB\-I\fP \fIdirectory\fP" +.Ix 0 def flags -I +Tells PMake another place to search for included makefiles. Yet +another thing to be explained in chapter 3 (section 3.2, to be +precise). +.Rm 2 3.2 +.IP "\fB\-J\fP \fInumber\fP" +.Ix 0 def flags -J +Gives the absolute maximum number of targets to create at once on both +local and remote machines. +.IP "\fB\-L\fP \fInumber\fP" +.Ix 0 def flags -L +This specifies the maximum number of targets to create on the local +machine at once. This may be 0, though you should be wary of doing +this, as PMake may hang until a remote machine becomes available, if +one is not available when it is started. +.IP \fB\-M\fP +.Ix 0 ref compatibility +.Ix 0 def flags -M +This is the flag that provides absolute, complete, full compatibility +with Make. It still allows you to use all but a few of the features of +PMake, but it is non-parallel. This is the mode PMake enters if you +call it +.CW make .'' `` +.IP \fB\-P\fP +.Ix 0 def flags -P +.Ix 0 ref "output control" +When creating targets in parallel, several shells are executing at +once, each wanting to write its own two cent's-worth to the screen. +This output must be captured by PMake in some way in order to prevent +the screen from being filled with garbage even more indecipherable +than you usually see. PMake has two ways of doing this, one of which +provides for much cleaner output and a clear separation between the +output of different jobs, the other of which provides a more immediate +response so one can tell what is really happening. The former is done +by notifying you when the creation of a target starts, capturing the +output and transferring it to the screen all at once when the job +finishes. The latter is done by catching the output of the shell (and +its children) and buffering it until an entire line is received, then +printing that line preceded by an indication of which job produced +the output. Since I prefer this second method, it is the one used by +default. The first method will be used if you give the +.B \-P +flag to PMake. +.IP \fB\-V\fP +.Ix 0 def flags -V +As mentioned before, the +.B \-V +flag tells PMake to use Make's style of expanding variables, +substituting the empty string for any variable it doesn't know. +.IP \fB\-W\fP +.Ix 0 def flags -W +There are several times when PMake will print a message at you that is +only a warning, i.e. it can continue to work in spite of your having +done something silly (such as forgotten a leading tab for a shell +command). Sometimes you are well aware of silly things you have done +and would like PMake to stop bothering you. This flag tells it to shut +up about anything non-fatal. +.IP \fB\-X\fP +.Ix 0 def flags -X +This flag causes PMake to not attempt to export any jobs to another +machine. +.LP +Several flags may follow a single `\-'. Those flags that require +arguments take them from successive parameters. E.g. +.DS +pmake -fDnI server.mk DEBUG /chip2/X/server/include +.DE +will cause PMake to read +.CW server.mk +as the input makefile, define the variable +.CW DEBUG +as a global variable and look for included makefiles in the directory +.CW /chip2/X/server/include . +.xH 2 Summary +.LP +A makefile is made of four types of lines: +.RS +.IP \(bu 2 +Dependency lines +.IP \(bu 2 +Creation commands +.IP \(bu 2 +Variable assignments +.IP \(bu 2 +Comments, include statements and conditional directives +.RE +.LP +A dependency line is a list of one or more targets, an operator +.CW : ', (` +.CW :: ', ` +or +.CW ! '), ` +and a list of zero or more sources. Sources may contain wildcards and +certain local variables. +.LP +A creation command is a regular shell command preceded by a tab. In +addition, if the first two characters after the tab (and other +whitespace) are a combination of +.CW @ ' ` +or +.CW - ', ` +PMake will cause the command to not be printed (if the character is +.CW @ ') ` +or errors from it to be ignored (if +.CW - '). ` +A blank line, dependency line or variable assignment terminates a +creation script. There may be only one creation script for each target +with a +.CW : ' ` +or +.CW ! ' ` +operator. +.LP +Variables are places to store text. They may be unconditionally +assigned-to using the +.CW = ' ` +.Ix 0 ref = +.Ix 0 ref variable assignment +operator, appended-to using the +.CW += ' ` +.Ix 0 ref += +.Ix 0 ref variable assignment appended +operator, conditionally (if the variable is undefined) assigned-to +with the +.CW ?= ' ` +.Ix 0 ref ?= +.Ix 0 ref variable assignment conditional +operator, and assigned-to with variable expansion with the +.CW := ' ` +.Ix 0 ref := +.Ix 0 ref variable assignment expanded +operator. The output of a shell command may be assigned to a variable +using the +.CW != ' ` +.Ix 0 ref != +.Ix 0 ref variable assignment shell-output +operator. Variables may be expanded (their value inserted) by enclosing +their name in parentheses or curly braces, preceded by a dollar sign. +A dollar sign may be escaped with another dollar sign. Variables are +not expanded if PMake doesn't know about them. There are seven local +variables: +.CW .TARGET , +.CW .ALLSRC , +.CW .OODATE , +.CW .PREFIX , +.CW .IMPSRC , +.CW .ARCHIVE , +and +.CW .MEMBER . +Four of them +.CW .TARGET , ( +.CW .PREFIX , +.CW .ARCHIVE , +and +.CW .MEMBER ) +may be used to specify ``dynamic sources.'' +.Ix 0 ref "dynamic source" +.Ix 0 ref source dynamic +Variables are good. Know them. Love them. Live them. +.LP +Debugging of makefiles is best accomplished using the +.B \-n , +.B "\-d m" , +and +.B "\-p 2" +flags. +.xH 2 Exercises +.ce +\s+4\fBTBA\fP\s0 +.xH 1 Short-cuts and Other Nice Things +.LP +Based on what I've told you so far, you may have gotten the impression +that PMake is just a way of storing away commands and making sure you +don't forget to compile something. Good. That's just what it is. +However, the ways I've described have been inelegant, at best, and +painful, at worst. +This chapter contains things that make the +writing of makefiles easier and the makefiles themselves shorter and +easier to modify (and, occasionally, simpler). In this chapter, I +assume you are somewhat more +familiar with Sprite (or UNIX, if that's what you're using) than I did +in chapter 2, just so you're on your toes. +So without further ado... +.xH 2 Transformation Rules +.LP +As you know, a file's name consists of two parts: a base name, which +gives some hint as to the contents of the file, and a suffix, which +usually indicates the format of the file. +Over the years, as +.UX +has developed, +naming conventions, with regard to suffixes, have also developed that have +become almost as incontrovertible as Law. E.g. a file ending in +.CW .c +is assumed to contain C source code; one with a +.CW .o +suffix is assumed to be a compiled, relocatable object file that may +be linked into any program; a file with a +.CW .ms +suffix is usually a text file to be processed by Troff with the \-ms +macro package, and so on. +One of the best aspects of both Make and PMake comes from their +understanding of how the suffix of a file pertains to its contents and +their ability to do things with a file based solely on its suffix. This +ability comes from something known as a transformation rule. A +transformation rule specifies how to change a file with one suffix +into a file with another suffix. +.LP +A transformation rule looks much like a dependency line, except the +target is made of two known suffixes stuck together. Suffixes are made +known to PMake by placing them as sources on a dependency line whose +target is the special target +.CW .SUFFIXES . +E.g. +.DS +\&.SUFFIXES : .o .c +\&.c.o : + $(CC) $(CFLAGS) -c $(.IMPSRC) +.DE +The creation script attached to the target is used to transform a file with +the first suffix (in this case, +.CW .c ) +into a file with the second suffix (here, +.CW .o ). +In addition, the target inherits whatever attributes have been applied +to the transformation rule. +The simple rule given above says that to transform a C source file +into an object file, you compile it using +.CW cc +with the +.CW \-c +flag. +This rule is taken straight from the system makefile. Many +transformation rules (and suffixes) are defined there, and I refer you +to it for more examples (type +.CW "pmake -h" '' `` +to find out where it is). +.LP +There are several things to note about the transformation rule given +above: +.RS +.IP 1) +The +.CW .IMPSRC +variable. +.Ix 0 def variable local .IMPSRC +.Ix 0 def .IMPSRC +This variable is set to the ``implied source'' (the file from which +the target is being created; the one with the first suffix), which, in this +case, is the .c file. +.IP 2) +The +.CW CFLAGS +variable. Almost all of the transformation rules in the system +makefile are set up using variables that you can alter in your +makefile to tailor the rule to your needs. In this case, if you want +all your C files to be compiled with the +.B \-g +flag, to provide information for +.CW dbx , +you would set the +.CW CFLAGS +variable to contain +.CW -g +.CW "CFLAGS = -g" '') (`` +and PMake would take care of the rest. +.RE +.LP +To give you a quick example, the makefile in 2.3.4 +.Rm 3 2.3.4 +could be changed to this: +.DS +OBJS = a.o b.o c.o +program : $(OBJS) + $(CC) -o $(.TARGET) $(.ALLSRC) +$(OBJS) : defs.h +.DE +The transformation rule I gave above takes the place of the 6 lines\** +.FS +This is also somewhat cleaner, I think, than the dynamic source +solution presented in 2.6 +.FE +.Rm 4 2.6 +.DS +a.o : a.c + cc -c a.c +b.o : b.c + cc -c b.c +c.o : c.c + cc -c c.c +.DE +.LP +Now you may be wondering about the dependency between the +.CW .o +and +.CW .c +files \*- it's not mentioned anywhere in the new makefile. This is +because it isn't needed: one of the effects of applying a +transformation rule is the target comes to depend on the implied +source. That's why it's called the implied +.I source . +.LP +For a more detailed example. Say you have a makefile like this: +.DS +a.out : a.o b.o + $(CC) $(.ALLSRC) +.DE +and a directory set up like this: +.DS +total 4 +-rw-rw-r-- 1 deboor 34 Sep 7 00:43 Makefile +-rw-rw-r-- 1 deboor 119 Oct 3 19:39 a.c +-rw-rw-r-- 1 deboor 201 Sep 7 00:43 a.o +-rw-rw-r-- 1 deboor 69 Sep 7 00:43 b.c +.DE +While just typing +.CW pmake '' `` +will do the right thing, it's much more informative to type +.CW "pmake -d s" ''. `` +This will show you what PMake is up to as it processes the files. In +this case, PMake prints the following: +.DS +Suff_FindDeps (a.out) + using existing source a.o + applying .o -> .out to "a.o" +Suff_FindDeps (a.o) + trying a.c...got it + applying .c -> .o to "a.c" +Suff_FindDeps (b.o) + trying b.c...got it + applying .c -> .o to "b.c" +Suff_FindDeps (a.c) + trying a.y...not there + trying a.l...not there + trying a.c,v...not there + trying a.y,v...not there + trying a.l,v...not there +Suff_FindDeps (b.c) + trying b.y...not there + trying b.l...not there + trying b.c,v...not there + trying b.y,v...not there + trying b.l,v...not there +--- a.o --- +cc -c a.c +--- b.o --- +cc -c b.c +--- a.out --- +cc a.o b.o +.DE +.LP +.CW Suff_FindDeps +is the name of a function in PMake that is called to check for implied +sources for a target using transformation rules. +The transformations it tries are, naturally +enough, limited to the ones that have been defined (a transformation +may be defined multiple times, by the way, but only the most recent +one will be used). You will notice, however, that there is a definite +order to the suffixes that are tried. This order is set by the +relative positions of the suffixes on the +.CW .SUFFIXES +line \*- the earlier a suffix appears, the earlier it is checked as +the source of a transformation. Once a suffix has been defined, the +only way to change its position in the pecking order is to remove all +the suffixes (by having a +.CW .SUFFIXES +dependency line with no sources) and redefine them in the order you +want. (Previously-defined transformation rules will be automatically +redefined as the suffixes they involve are re-entered.) +.LP +Another way to affect the search order is to make the dependency +explicit. In the above example, +.CW a.out +depends on +.CW a.o +and +.CW b.o . +Since a transformation exists from +.CW .o +to +.CW .out , +PMake uses that, as indicated by the +.CW "using existing source a.o" '' `` +message. +.LP +The search for a transformation starts from the suffix of the target +and continues through all the defined transformations, in the order +dictated by the suffix ranking, until an existing file with the same +base (the target name minus the suffix and any leading directories) is +found. At that point, one or more transformation rules will have been +found to change the one existing file into the target. +.LP +For example, ignoring what's in the system makefile for now, say you +have a makefile like this: +.DS +\&.SUFFIXES : .out .o .c .y .l +\&.l.c : + lex $(.IMPSRC) + mv lex.yy.c $(.TARGET) +\&.y.c : + yacc $(.IMPSRC) + mv y.tab.c $(.TARGET) +\&.c.o : + cc -c $(.IMPSRC) +\&.o.out : + cc -o $(.TARGET) $(.IMPSRC) +.DE +and the single file +.CW jive.l . +If you were to type +.CW "pmake -rd ms jive.out" ,'' `` +you would get the following output for +.CW jive.out : +.DS +Suff_FindDeps (jive.out) + trying jive.o...not there + trying jive.c...not there + trying jive.y...not there + trying jive.l...got it + applying .l -> .c to "jive.l" + applying .c -> .o to "jive.c" + applying .o -> .out to "jive.o" +.DE +and this is why: PMake starts with the target +.CW jive.out , +figures out its suffix +.CW .out ) ( +and looks for things it can transform to a +.CW .out +file. In this case, it only finds +.CW .o , +so it looks for the file +.CW jive.o . +It fails to find it, so it looks for transformations into a +.CW .o +file. Again it has only one choice: +.CW .c . +So it looks for +.CW jive.c +and, as you know, fails to find it. At this point it has two choices: +it can create the +.CW .c +file from either a +.CW .y +file or a +.CW .l +file. Since +.CW .y +came first on the +.CW .SUFFIXES +line, it checks for +.CW jive.y +first, but can't find it, so it looks for +.CW jive.l +and, lo and behold, there it is. +At this point, it has defined a transformation path as follows: +.CW .l +\(-> +.CW .c +\(-> +.CW .o +\(-> +.CW .out +and applies the transformation rules accordingly. For completeness, +and to give you a better idea of what PMake actually did with this +three-step transformation, this is what PMake printed for the rest of +the process: +.DS +Suff_FindDeps (jive.o) + using existing source jive.c + applying .c -> .o to "jive.c" +Suff_FindDeps (jive.c) + using existing source jive.l + applying .l -> .c to "jive.l" +Suff_FindDeps (jive.l) +Examining jive.l...modified 17:16:01 Oct 4, 1987...up-to-date +Examining jive.c...non-existent...out-of-date +--- jive.c --- +lex jive.l +\&.\|.\|. meaningless lex output deleted .\|.\|. +mv lex.yy.c jive.c +Examining jive.o...non-existent...out-of-date +--- jive.o --- +cc -c jive.c +Examining jive.out...non-existent...out-of-date +--- jive.out --- +cc -o jive.out jive.o +.DE +.LP +One final question remains: what does PMake do with targets that have +no known suffix? PMake simply pretends it actually has a known suffix +and searches for transformations accordingly. +The suffix it chooses is the source for the +.CW .NULL +.Ix 0 ref .NULL +target mentioned later. In the system makefile, +.CW .out +is chosen as the ``null suffix'' +.Ix 0 def suffix null +.Ix 0 def "null suffix" +because most people use PMake to create programs. You are, however, +free and welcome to change it to a suffix of your own choosing. +The null suffix is ignored, however, when PMake is in compatibility +mode (see chapter 4). +.xH 2 Including Other Makefiles +.Ix 0 def makefile inclusion +.Rd 2 +.LP +Just as for programs, it is often useful to extract certain parts of a +makefile into another file and just include it in other makefiles +somehow. Many compilers allow you say something like +.DS +#include "defs.h" +.DE +to include the contents of +.CW defs.h +in the source file. PMake allows you to do the same thing for +makefiles, with the added ability to use variables in the filenames. +An include directive in a makefile looks either like this: +.DS +#include +.DE +or this +.DS +#include "file" +.DE +The difference between the two is where PMake searches for the file: +the first way, PMake will look for +the file only in the system makefile directory (or directories) +(to find out what that directory is, give PMake the +.B \-h +flag). +.Ix 0 ref flags -h +The system makefile directory search path can be overridden via the +.B \-m +option. +.Ix 0 ref flags -m +For files in double-quotes, the search is more complex: +.RS +.IP 1) +The directory of the makefile that's including the file. +.IP 2) +The current directory (the one in which you invoked PMake). +.IP 3) +The directories given by you using +.B \-I +flags, in the order in which you gave them. +.IP 4) +Directories given by +.CW .PATH +dependency lines (see chapter 4). +.IP 5) +The system makefile directory. +.RE +.LP +in that order. +.LP +You are free to use PMake variables in the filename\*-PMake will +expand them before searching for the file. You must specify the +searching method with either angle brackets or double-quotes +.I outside +of a variable expansion. I.e. the following +.DS +SYSTEM = + +#include $(SYSTEM) +.DE +won't work. +.xH 2 Saving Commands +.LP +.Ix 0 def ... +There may come a time when you will want to save certain commands to +be executed when everything else is done. For instance: you're +making several different libraries at one time and you want to create the +members in parallel. Problem is, +.CW ranlib +is another one of those programs that can't be run more than once in +the same directory at the same time (each one creates a file called +.CW __.SYMDEF +into which it stuffs information for the linker to use. Two of them +running at once will overwrite each other's file and the result will +be garbage for both parties). You might want a way to save the ranlib +commands til the end so they can be run one after the other, thus +keeping them from trashing each other's file. PMake allows you to do +this by inserting an ellipsis (``.\|.\|.'') as a command between +commands to be run at once and those to be run later. +.LP +So for the +.CW ranlib +case above, you might do this: +.Rd 5 +.DS +lib1.a : $(LIB1OBJS) + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) + +lib2.a : $(LIB2OBJS) + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +This would save both +.DS +ranlib $(.TARGET) +.DE +commands until the end, when they would run one after the other +(using the correct value for the +.CW .TARGET +variable, of course). +.LP +Commands saved in this manner are only executed if PMake manages to +re-create everything without an error. +.xH 2 Target Attributes +.LP +PMake allows you to give attributes to targets by means of special +sources. Like everything else PMake uses, these sources begin with a +period and are made up of all upper-case letters. There are various +reasons for using them, and I will try to give examples for most of +them. Others you'll have to find uses for yourself. Think of it as ``an +exercise for the reader.'' By placing one (or more) of these as a source on a +dependency line, you are ``marking the target(s) with that +attribute.'' That's just the way I phrase it, so you know. +.LP +Any attributes given as sources for a transformation rule are applied +to the target of the transformation rule when the rule is applied. +.Ix 0 def attributes +.Ix 0 ref source +.Ix 0 ref target +.nr pw 12 +.IP .DONTCARE \n(pw +.Ix 0 def attributes .DONTCARE +.Ix 0 def .DONTCARE +If a target is marked with this attribute and PMake can't figure out +how to create it, it will ignore this fact and assume the file isn't +really needed or actually exists and PMake just can't find it. This may prove +wrong, but the error will be noted later on, not when PMake tries to create +the target so marked. This attribute also prevents PMake from +attempting to touch the target if it is given the +.B \-t +flag. +.Ix 0 ref flags -t +.IP .EXEC \n(pw +.Ix 0 def attributes .EXEC +.Ix 0 def .EXEC +This attribute causes its shell script to be executed while having no +effect on targets that depend on it. This makes the target into a sort +of subroutine. An example. Say you have some LISP files that need to +be compiled and loaded into a LISP process. To do this, you echo LISP +commands into a file and execute a LISP with this file as its input +when everything's done. Say also that you have to load other files +from another system before you can compile your files and further, +that you don't want to go through the loading and dumping unless one +of +.I your +files has changed. Your makefile might look a little bit +like this (remember, this is an educational example, and don't worry +about the +.CW COMPILE +rule, all will soon become clear, grasshopper): +.DS +system : init a.fasl b.fasl c.fasl + for i in $(.ALLSRC); + do + echo -n '(load "' >> input + echo -n ${i} >> input + echo '")' >> input + done + echo '(dump "$(.TARGET)")' >> input + lisp < input + +a.fasl : a.l init COMPILE +b.fasl : b.l init COMPILE +c.fasl : c.l init COMPILE +COMPILE : .USE + echo '(compile "$(.ALLSRC)")' >> input +init : .EXEC + echo '(load-system)' > input +.DE +.Ix 0 ref .USE +.Ix 0 ref attributes .USE +.Ix 0 ref variable local .ALLSRC +.IP "\&" +.CW .EXEC +sources, don't appear in the local variables of targets that depend on +them (nor are they touched if PMake is given the +.B \-t +flag). +.Ix 0 ref flags -t +Note that all the rules, not just that for +.CW system , +include +.CW init +as a source. This is because none of the other targets can be made +until +.CW init +has been made, thus they depend on it. +.IP .EXPORT \n(pw +.Ix 0 def attributes .EXPORT +.Ix 0 def .EXPORT +This is used to mark those targets whose creation should be sent to +another machine if at all possible. This may be used by some +exportation schemes if the exportation is expensive. You should ask +your system administrator if it is necessary. +.IP .EXPORTSAME \n(pw +.Ix 0 def attributes .EXPORTSAME +.Ix 0 def .EXPORTSAME +Tells the export system that the job should be exported to a machine +of the same architecture as the current one. Certain operations (e.g. +running text through +.CW nroff ) +can be performed the same on any architecture (CPU and +operating system type), while others (e.g. compiling a program with +.CW cc ) +must be performed on a machine with the same architecture. Not all +export systems will support this attribute. +.IP .IGNORE \n(pw +.Ix 0 def attributes .IGNORE +.Ix 0 def .IGNORE attribute +Giving a target the +.CW .IGNORE +attribute causes PMake to ignore errors from any of the target's commands, as +if they all had `\-' before them. +.IP .INVISIBLE \n(pw +.Ix 0 def attributes .INVISIBLE +.Ix 0 def .INVISIBLE +This allows you to specify one target as a source for another without +the one affecting the other's local variables. Useful if, say, you +have a makefile that creates two programs, one of which is used to +create the other, so it must exist before the other is created. You +could say +.DS +prog1 : $(PROG1OBJS) prog2 MAKEINSTALL +prog2 : $(PROG2OBJS) .INVISIBLE MAKEINSTALL +.DE +where +.CW MAKEINSTALL +is some complex .USE rule (see below) that depends on the +.Ix 0 ref .USE +.CW .ALLSRC +variable containing the right things. Without the +.CW .INVISIBLE +attribute for +.CW prog2 , +the +.CW MAKEINSTALL +rule couldn't be applied. This is not as useful as it should be, and +the semantics may change (or the whole thing go away) in the +not-too-distant future. +.IP .JOIN \n(pw +.Ix 0 def attributes .JOIN +.Ix 0 def .JOIN +This is another way to avoid performing some operations in parallel +while permitting everything else to be done so. Specifically it +forces the target's shell script to be executed only if one or more of the +sources was out-of-date. In addition, the target's name, +in both its +.CW .TARGET +variable and all the local variables of any target that depends on it, +is replaced by the value of its +.CW .ALLSRC +variable. +As an example, suppose you have a program that has four libraries that +compile in the same directory along with, and at the same time as, the +program. You again have the problem with +.CW ranlib +that I mentioned earlier, only this time it's more severe: you +can't just put the ranlib off to the end since the program +will need those libraries before it can be re-created. You can do +something like this: +.DS +program : $(OBJS) libraries + cc -o $(.TARGET) $(.ALLSRC) + +libraries : lib1.a lib2.a lib3.a lib4.a .JOIN + ranlib $(.OODATE) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +.Ix 0 ref variable local .OODATE +.Ix 0 ref .TARGET +.Ix 0 ref .ALLSRC +.Ix 0 ref .OODATE +In this case, PMake will re-create the +.CW $(OBJS) +as necessary, along with +.CW lib1.a , +.CW lib2.a , +.CW lib3.a +and +.CW lib4.a . +It will then execute +.CW ranlib +on any library that was changed and set +.CW program 's +.CW .ALLSRC +variable to contain what's in +.CW $(OBJS) +followed by +.CW "lib1.a lib2.a lib3.a lib4.a" .'' `` +In case you're wondering, it's called +.CW .JOIN +because it joins together different threads of the ``input graph'' at +the target marked with the attribute. +Another aspect of the .JOIN attribute is it keeps the target from +being created if the +.B \-t +flag was given. +.Ix 0 ref flags -t +.IP .MAKE \n(pw +.Ix 0 def attributes .MAKE +.Ix 0 def .MAKE +The +.CW .MAKE +attribute marks its target as being a recursive invocation of PMake. +This forces PMake to execute the script associated with the target (if +it's out-of-date) even if you gave the +.B \-n +or +.B \-t +flag. By doing this, you can start at the top of a system and type +.DS +pmake -n +.DE +and have it descend the directory tree (if your makefiles are set up +correctly), printing what it would have executed if you hadn't +included the +.B \-n +flag. +.IP .NOEXPORT \n(pw +.Ix 0 def attributes .NOEXPORT +.Ix 0 def .NOEXPORT attribute +If possible, PMake will attempt to export the creation of all targets to +another machine (this depends on how PMake was configured). Sometimes, +the creation is so simple, it is pointless to send it to another +machine. If you give the target the +.CW .NOEXPORT +attribute, it will be run locally, even if you've given PMake the +.B "\-L 0" +flag. +.IP .NOTMAIN \n(pw +.Ix 0 def attributes .NOTMAIN +.Ix 0 def .NOTMAIN +Normally, if you do not specify a target to make in any other way, +PMake will take the first target on the first dependency line of a +makefile as the target to create. That target is known as the ``Main +Target'' and is labeled as such if you print the dependencies out +using the +.B \-p +flag. +.Ix 0 ref flags -p +Giving a target this attribute tells PMake that the target is +definitely +.I not +the Main Target. +This allows you to place targets in an included makefile and +have PMake create something else by default. +.IP .PRECIOUS \n(pw +.Ix 0 def attributes .PRECIOUS +.Ix 0 def .PRECIOUS attribute +When PMake is interrupted (you type control-C at the keyboard), it +will attempt to clean up after itself by removing any half-made +targets. If a target has the +.CW .PRECIOUS +attribute, however, PMake will leave it alone. An additional side +effect of the `::' operator is to mark the targets as +.CW .PRECIOUS . +.Ix 0 ref operator double-colon +.Ix 0 ref :: +.IP .SILENT \n(pw +.Ix 0 def attributes .SILENT +.Ix 0 def .SILENT attribute +Marking a target with this attribute keeps its commands from being +printed when they're executed, just as if they had an `@' in front of them. +.IP .USE \n(pw +.Ix 0 def attributes .USE +.Ix 0 def .USE +By giving a target this attribute, you turn it into PMake's equivalent +of a macro. When the target is used as a source for another target, +the other target acquires the commands, sources and attributes (except +.CW .USE ) +of the source. +If the target already has commands, the +.CW .USE +target's commands are added to the end. If more than one .USE-marked +source is given to a target, the rules are applied sequentially. +.IP "\&" \n(pw +The typical .USE rule (as I call them) will use the sources of the +target to which it is applied (as stored in the +.CW .ALLSRC +variable for the target) as its ``arguments,'' if you will. +For example, you probably noticed that the commands for creating +.CW lib1.a +and +.CW lib2.a +in the example in section 3.3 +.Rm 5 3.3 +were exactly the same. You can use the +.CW .USE +attribute to eliminate the repetition, like so: +.DS +lib1.a : $(LIB1OBJS) MAKELIB +lib2.a : $(LIB2OBJS) MAKELIB + +MAKELIB : .USE + rm -f $(.TARGET) + ar cr $(.TARGET) $(.ALLSRC) + ... + ranlib $(.TARGET) +.DE +.Ix 0 ref variable local .TARGET +.Ix 0 ref variable local .ALLSRC +.IP "\&" \n(pw +Several system makefiles (not to be confused with The System Makefile) +make use of these .USE rules to make your +life easier (they're in the default, system makefile directory...take a look). +Note that the .USE rule source itself +.CW MAKELIB ) ( +does not appear in any of the targets's local variables. +There is no limit to the number of times I could use the +.CW MAKELIB +rule. If there were more libraries, I could continue with +.CW "lib3.a : $(LIB3OBJS) MAKELIB" '' `` +and so on and so forth. +.xH 2 Special Targets +.LP +As there were in Make, so there are certain targets that have special +meaning to PMake. When you use one on a dependency line, it is the +only target that may appear on the left-hand-side of the operator. +.Ix 0 ref target +.Ix 0 ref operator +As for the attributes and variables, all the special targets +begin with a period and consist of upper-case letters only. +I won't describe them all in detail because some of them are rather +complex and I'll describe them in more detail than you'll want in +chapter 4. +The targets are as follows: +.nr pw 10 +.IP .BEGIN \n(pw +.Ix 0 def .BEGIN +Any commands attached to this target are executed before anything else +is done. You can use it for any initialization that needs doing. +.IP .DEFAULT \n(pw +.Ix 0 def .DEFAULT +This is sort of a .USE rule for any target (that was used only as a +source) that PMake can't figure out any other way to create. It's only +``sort of'' a .USE rule because only the shell script attached to the +.CW .DEFAULT +target is used. The +.CW .IMPSRC +variable of a target that inherits +.CW .DEFAULT 's +commands is set to the target's own name. +.Ix 0 ref .IMPSRC +.Ix 0 ref variable local .IMPSRC +.IP .END \n(pw +.Ix 0 def .END +This serves a function similar to +.CW .BEGIN , +in that commands attached to it are executed once everything has been +re-created (so long as no errors occurred). It also serves the extra +function of being a place on which PMake can hang commands you put off +to the end. Thus the script for this target will be executed before +any of the commands you save with the ``.\|.\|.''. +.Ix 0 ref ... +.IP .EXPORT \n(pw +The sources for this target are passed to the exportation system compiled +into PMake. Some systems will use these sources to configure +themselves. You should ask your system administrator about this. +.IP .IGNORE \n(pw +.Ix 0 def .IGNORE target +.Ix 0 ref .IGNORE attribute +.Ix 0 ref attributes .IGNORE +This target marks each of its sources with the +.CW .IGNORE +attribute. If you don't give it any sources, then it is like +giving the +.B \-i +flag when you invoke PMake \*- errors are ignored for all commands. +.Ix 0 ref flags -i +.IP .INCLUDES \n(pw +.Ix 0 def .INCLUDES target +.Ix 0 def variable global .INCLUDES +.Ix 0 def .INCLUDES variable +The sources for this target are taken to be suffixes that indicate a +file that can be included in a program source file. +The suffix must have already been declared with +.CW .SUFFIXES +(see below). +Any suffix so marked will have the directories on its search path +(see +.CW .PATH , +below) placed in the +.CW .INCLUDES +variable, each preceded by a +.B \-I +flag. This variable can then be used as an argument for the compiler +in the normal fashion. The +.CW .h +suffix is already marked in this way in the system makefile. +.Ix 0 ref makefile system +E.g. if you have +.DS +\&.SUFFIXES : .bitmap +\&.PATH.bitmap : /usr/local/X/lib/bitmaps +\&.INCLUDES : .bitmap +.DE +PMake will place +.CW "-I/usr/local/X/lib/bitmaps" '' `` +in the +.CW .INCLUDES +variable and you can then say +.DS +cc $(.INCLUDES) -c xprogram.c +.DE +(Note: the +.CW .INCLUDES +variable is not actually filled in until the entire makefile has been read.) +.IP .INTERRUPT \n(pw +.Ix 0 def .INTERRUPT +When PMake is interrupted, +it will execute the commands in the script for this target, if it +exists. +.IP .LIBS \n(pw +.Ix 0 def .LIBS target +.Ix 0 def .LIBS variable +.Ix 0 def variable global .LIBS +This does for libraries what +.CW .INCLUDES +does for include files, except the flag used is +.B \-L , +as required by those linkers that allow you to tell them where to find +libraries. The variable used is +.CW .LIBS . +Be forewarned that PMake may not have been compiled to do this if the +linker on your system doesn't accept the +.B \-L +flag, though the +.CW .LIBS +variable will always be defined once the makefile has been read. +.IP .MAIN \n(pw +.Ix 0 def .MAIN +If you didn't give a target (or targets) to create when you invoked +PMake, it will take the sources of this target as the targets to +create. +.IP .MAKEFLAGS \n(pw +.Ix 0 def .MAKEFLAGS target +This target provides a way for you to always specify flags for PMake +when the makefile is used. The flags are just as they would be typed +to the shell (except you can't use shell variables unless they're in +the environment), +though the +.B \-f +and +.B \-r +flags have no effect. +.IP .NULL \n(pw +.Ix 0 def .NULL +.Ix 0 ref suffix null +.Ix 0 ref "null suffix" +This allows you to specify what suffix PMake should pretend a file has +if, in fact, it has no known suffix. Only one suffix may be so +designated. The last source on the dependency line is the suffix that +is used (you should, however, only give one suffix.\|.\|.). +.IP .PATH \n(pw +.Ix 0 def .PATH +If you give sources for this target, PMake will take them as +directories in which to search for files it cannot find in the current +directory. If you give no sources, it will clear out any directories +added to the search path before. Since the effects of this all get +very complex, I'll leave it til chapter four to give you a complete +explanation. +.IP .PATH\fIsuffix\fP \n(pw +.Ix 0 ref .PATH +This does a similar thing to +.CW .PATH , +but it does it only for files with the given suffix. The suffix must +have been defined already. Look at +.B "Search Paths" +(section 4.1) +.Rm 6 4.1 +for more information. +.IP .PRECIOUS \n(pw +.Ix 0 def .PRECIOUS target +.Ix 0 ref .PRECIOUS attribute +.Ix 0 ref attributes .PRECIOUS +Similar to +.CW .IGNORE , +this gives the +.CW .PRECIOUS +attribute to each source on the dependency line, unless there are no +sources, in which case the +.CW .PRECIOUS +attribute is given to every target in the file. +.IP .RECURSIVE \n(pw +.Ix 0 def .RECURSIVE +.Ix 0 ref attributes .MAKE +.Ix 0 ref .MAKE +This target applies the +.CW .MAKE +attribute to all its sources. It does nothing if you don't give it any sources. +.IP .SHELL \n(pw +.Ix 0 def .SHELL +PMake is not constrained to only using the Bourne shell to execute +the commands you put in the makefile. You can tell it some other shell +to use with this target. Check out +.B "A Shell is a Shell is a Shell" +(section 4.4) +.Rm 7 4.4 +for more information. +.IP .SILENT \n(pw +.Ix 0 def .SILENT target +.Ix 0 ref .SILENT attribute +.Ix 0 ref attributes .SILENT +When you use +.CW .SILENT +as a target, it applies the +.CW .SILENT +attribute to each of its sources. If there are no sources on the +dependency line, then it is as if you gave PMake the +.B \-s +flag and no commands will be echoed. +.IP .SUFFIXES \n(pw +.Ix 0 def .SUFFIXES +This is used to give new file suffixes for PMake to handle. Each +source is a suffix PMake should recognize. If you give a +.CW .SUFFIXES +dependency line with no sources, PMake will forget about all the +suffixes it knew (this also nukes the null suffix). +For those targets that need to have suffixes defined, this is how you do it. +.LP +In addition to these targets, a line of the form +.DS +\fIattribute\fP : \fIsources\fP +.DE +applies the +.I attribute +to all the targets listed as +.I sources . +.xH 2 Modifying Variable Expansion +.LP +.Ix 0 def variable expansion modified +.Ix 0 ref variable expansion +.Ix 0 def variable modifiers +Variables need not always be expanded verbatim. PMake defines several +modifiers that may be applied to a variable's value before it is +expanded. You apply a modifier by placing it after the variable name +with a colon between the two, like so: +.DS +${\fIVARIABLE\fP:\fImodifier\fP} +.DE +Each modifier is a single character followed by something specific to +the modifier itself. +You may apply as many modifiers as you want \*- each one is applied to +the result of the previous and is separated from the previous by +another colon. +.LP +There are seven ways to modify a variable's expansion, most of which +come from the C shell variable modification characters: +.RS +.IP "M\fIpattern\fP" +.Ix 0 def :M +.Ix 0 def modifier match +This is used to select only those words (a word is a series of +characters that are neither spaces nor tabs) that match the given +.I pattern . +The pattern is a wildcard pattern like that used by the shell, where +.CW * +means 0 or more characters of any sort; +.CW ? +is any single character; +.CW [abcd] +matches any single character that is either `a', `b', `c' or `d' +(there may be any number of characters between the brackets); +.CW [0-9] +matches any single character that is between `0' and `9' (i.e. any +digit. This form may be freely mixed with the other bracket form), and +`\\' is used to escape any of the characters `*', `?', `[' or `:', +leaving them as regular characters to match themselves in a word. +For example, the system makefile +.CW +uses +.CW "$(CFLAGS:M-[ID]*)" '' `` +to extract all the +.CW \-I +and +.CW \-D +flags that would be passed to the C compiler. This allows it to +properly locate include files and generate the correct dependencies. +.IP "N\fIpattern\fP" +.Ix 0 def :N +.Ix 0 def modifier nomatch +This is identical to +.CW :M +except it substitutes all words that don't match the given pattern. +.IP "S/\fIsearch-string\fP/\fIreplacement-string\fP/[g]" +.Ix 0 def :S +.Ix 0 def modifier substitute +Causes the first occurrence of +.I search-string +in the variable to be replaced by +.I replacement-string , +unless the +.CW g +flag is given at the end, in which case all occurrences of the string +are replaced. The substitution is performed on each word in the +variable in turn. If +.I search-string +begins with a +.CW ^ , +the string must match starting at the beginning of the word. If +.I search-string +ends with a +.CW $ , +the string must match to the end of the word (these two may be +combined to force an exact match). If a backslash precedes these two +characters, however, they lose their special meaning. Variable +expansion also occurs in the normal fashion inside both the +.I search-string +and the +.I replacement-string , +.B except +that a backslash is used to prevent the expansion of a +.CW $ , +not another dollar sign, as is usual. +Note that +.I search-string +is just a string, not a pattern, so none of the usual +regular-expression/wildcard characters have any special meaning save +.CW ^ +and +.CW $ . +In the replacement string, +the +.CW & +character is replaced by the +.I search-string +unless it is preceded by a backslash. +You are allowed to use any character except +colon or exclamation point to separate the two strings. This so-called +delimiter character may be placed in either string by preceding it +with a backslash. +.IP T +.Ix 0 def :T +.Ix 0 def modifier tail +Replaces each word in the variable expansion by its last +component (its ``tail''). For example, given +.DS +OBJS = ../lib/a.o b /usr/lib/libm.a +TAILS = $(OBJS:T) +.DE +the variable +.CW TAILS +would expand to +.CW "a.o b libm.a" .'' `` +.IP H +.Ix 0 def :H +.Ix 0 def modifier head +This is similar to +.CW :T , +except that every word is replaced by everything but the tail (the +``head''). Using the same definition of +.CW OBJS , +the string +.CW "$(OBJS:H)" '' `` +would expand to +.CW "../lib /usr/lib" .'' `` +Note that the final slash on the heads is removed and +anything without a head is replaced by the empty string. +.IP E +.Ix 0 def :E +.Ix 0 def modifier extension +.Ix 0 def modifier suffix +.Ix 0 ref suffix "variable modifier" +.CW :E +replaces each word by its suffix (``extension''). So +.CW "$(OBJS:E)" '' `` +would give you +.CW ".o .a" .'' `` +.IP R +.Ix 0 def :R +.Ix 0 def modifier root +.Ix 0 def modifier base +This replaces each word by everything but the suffix (the ``root'' of +the word). +.CW "$(OBJS:R)" '' `` +expands to `` +.CW "../lib/a b /usr/lib/libm" .'' +.RE +.LP +In addition, the System V style of substitution is also supported. +This looks like: +.DS +$(\fIVARIABLE\fP:\fIsearch-string\fP=\fIreplacement\fP) +.DE +It must be the last modifier in the chain. The search is anchored at +the end of each word, so only suffixes or whole words may be replaced. +.xH 2 More on Debugging +.xH 2 More Exercises +.IP (3.1) +You've got a set programs, each of which is created from its own +assembly-language source file (suffix +.CW .asm ). +Each program can be assembled into two versions, one with error-checking +code assembled in and one without. You could assemble them into files +with different suffixes +.CW .eobj \& ( +and +.CW .obj , +for instance), but your linker only understands files that end in +.CW .obj . +To top it all off, the final executables +.I must +have the suffix +.CW .exe . +How can you still use transformation rules to make your life easier +(Hint: assume the error-checking versions have +.CW ec +tacked onto their prefix)? +.IP (3.2) +Assume, for a moment or two, you want to perform a sort of +``indirection'' by placing the name of a variable into another one, +then you want to get the value of the first by expanding the second +somehow. Unfortunately, PMake doesn't allow constructs like +.DS I +$($(FOO)) +.DE +What do you do? Hint: no further variable expansion is performed after +modifiers are applied, thus if you cause a $ to occur in the +expansion, that's what will be in the result. +.xH 1 PMake for Gods +.LP +This chapter is devoted to those facilities in PMake that allow you to +do a great deal in a makefile with very little work, as well as do +some things you couldn't do in Make without a great deal of work (and +perhaps the use of other programs). The problem with these features, +is they must be handled with care, or you will end up with a mess. +.LP +Once more, I assume a greater familiarity with +.UX +or Sprite than I did in the previous two chapters. +.xH 2 Search Paths +.Rd 6 +.LP +PMake supports the dispersal of files into multiple directories by +allowing you to specify places to look for sources with +.CW .PATH +targets in the makefile. The directories you give as sources for these +targets make up a ``search path.'' Only those files used exclusively +as sources are actually sought on a search path, the assumption being +that anything listed as a target in the makefile can be created by the +makefile and thus should be in the current directory. +.LP +There are two types of search paths +in PMake: one is used for all types of files (including included +makefiles) and is specified with a plain +.CW .PATH +target (e.g. +.CW ".PATH : RCS" ''), `` +while the other is specific to a certain type of file, as indicated by +the file's suffix. A specific search path is indicated by immediately following +the +.CW .PATH +with the suffix of the file. For instance +.DS +\&.PATH.h : /sprite/lib/include /sprite/att/lib/include +.DE +would tell PMake to look in the directories +.CW /sprite/lib/include +and +.CW /sprite/att/lib/include +for any files whose suffix is +.CW .h . +.LP +The current directory is always consulted first to see if a file +exists. Only if it cannot be found there are the directories in the +specific search path, followed by those in the general search path, +consulted. +.LP +A search path is also used when expanding wildcard characters. If the +pattern has a recognizable suffix on it, the path for that suffix will +be used for the expansion. Otherwise the default search path is employed. +.LP +When a file is found in some directory other than the current one, all +local variables that would have contained the target's name +.CW .ALLSRC , ( +and +.CW .IMPSRC ) +will instead contain the path to the file, as found by PMake. +Thus if you have a file +.CW ../lib/mumble.c +and a makefile +.DS +\&.PATH.c : ../lib +mumble : mumble.c + $(CC) -o $(.TARGET) $(.ALLSRC) +.DE +the command executed to create +.CW mumble +would be +.CW "cc -o mumble ../lib/mumble.c" .'' `` +(As an aside, the command in this case isn't strictly necessary, since +it will be found using transformation rules if it isn't given. This is because +.CW .out +is the null suffix by default and a transformation exists from +.CW .c +to +.CW .out . +Just thought I'd throw that in.) +.LP +If a file exists in two directories on the same search path, the file +in the first directory on the path will be the one PMake uses. So if +you have a large system spread over many directories, it would behoove +you to follow a naming convention that avoids such conflicts. +.LP +Something you should know about the way search paths are implemented +is that each directory is read, and its contents cached, exactly once +\&\*- when it is first encountered \*- so any changes to the +directories while PMake is running will not be noted when searching +for implicit sources, nor will they be found when PMake attempts to +discover when the file was last modified, unless the file was created in the +current directory. While people have suggested that PMake should read +the directories each time, my experience suggests that the caching seldom +causes problems. In addition, not caching the directories slows things +down enormously because of PMake's attempts to apply transformation +rules through non-existent files \*- the number of extra file-system +searches is truly staggering, especially if many files without +suffixes are used and the null suffix isn't changed from +.CW .out . +.xH 2 Archives and Libraries +.LP +.UX +and Sprite allow you to merge files into an archive using the +.CW ar +command. Further, if the files are relocatable object files, you can +run +.CW ranlib +on the archive and get yourself a library that you can link into any +program you want. The main problem with archives is they double the +space you need to store the archived files, since there's one copy in +the archive and one copy out by itself. The problem with libraries is +you usually think of them as +.CW -lm +rather than +.CW /usr/lib/libm.a +and the linker thinks they're out-of-date if you so much as look at +them. +.LP +PMake solves the problem with archives by allowing you to tell it to +examine the files in the archives (so you can remove the individual +files without having to regenerate them later). To handle the problem +with libraries, PMake adds an additional way of deciding if a library +is out-of-date: +.IP \(bu 2 +If the table of contents is older than the library, or is missing, the +library is out-of-date. +.LP +A library is any target that looks like +.CW \-l name'' `` +or that ends in a suffix that was marked as a library using the +.CW .LIBS +target. +.CW .a +is so marked in the system makefile. +.LP +Members of an archive are specified as +``\fIarchive\fP(\fImember\fP[ \fImember\fP...])''. +Thus +.CW libdix.a(window.o) '' ``' +specifies the file +.CW window.o +in the archive +.CW libdix.a . +You may also use wildcards to specify the members of the archive. Just +remember that most the wildcard characters will only find +.I existing +files. +.LP +A file that is a member of an archive is treated specially. If the +file doesn't exist, but it is in the archive, the modification time +recorded in the archive is used for the file when determining if the +file is out-of-date. When figuring out how to make an archived member target +(not the file itself, but the file in the archive \*- the +\fIarchive\fP(\fImember\fP) target), special care is +taken with the transformation rules, as follows: +.IP \(bu 2 +\&\fIarchive\fP(\fImember\fP) is made to depend on \fImember\fP. +.IP \(bu 2 +The transformation from the \fImember\fP's suffix to the +\fIarchive\fP's suffix is applied to the \fIarchive\fP(\fImember\fP) target. +.IP \(bu 2 +The \fIarchive\fP(\fImember\fP)'s +.CW .TARGET +variable is set to the name of the \fImember\fP if \fImember\fP is +actually a target, or the path to the member file if \fImember\fP is +only a source. +.IP \(bu 2 +The +.CW .ARCHIVE +variable for the \fIarchive\fP(\fImember\fP) target is set to the name +of the \fIarchive\fP. +.Ix 0 def variable local .ARCHIVE +.Ix 0 def .ARCHIVE +.IP \(bu 2 +The +.CW .MEMBER +variable is set to the actual string inside the parentheses. In most +cases, this will be the same as the +.CW .TARGET +variable. +.Ix 0 def variable local .MEMBER +.Ix 0 def .MEMBER +.IP \(bu 2 +The \fIarchive\fP(\fImember\fP)'s place in the local variables of the +targets that depend on it is taken by the value of its +.CW .TARGET +variable. +.LP +Thus, a program library could be created with the following makefile: +.DS +\&.o.a : + ... + rm -f $(.TARGET:T) +OBJS = obj1.o obj2.o obj3.o +libprog.a : libprog.a($(OBJS)) + ar cru $(.TARGET) $(.OODATE) + ranlib $(.TARGET) +.DE +This will cause the three object files to be compiled (if the +corresponding source files were modified after the object file or, if +that doesn't exist, the archived object file), the out-of-date ones +archived in +.CW libprog.a , +a table of contents placed in the archive and the newly-archived +object files to be removed. +.LP +All this is used in the +.CW makelib.mk +system makefile to create a single library with ease. This makefile +looks like this: +.DS +.SM +# +# Rules for making libraries. The object files that make up the library +# are removed once they are archived. +# +# To make several libraries in parallel, you should define the variable +# "many_libraries". This will serialize the invocations of ranlib. +# +# To use, do something like this: +# +# OBJECTS = +# +# fish.a: fish.a($(OBJECTS)) MAKELIB +# +# + +#ifndef _MAKELIB_MK +_MAKELIB_MK = + +#include + +\&.po.a .o.a : + ... + rm -f $(.MEMBER) + +ARFLAGS ?= crl + +# +# Re-archive the out-of-date members and recreate the library's table of +# contents using ranlib. If many_libraries is defined, put the ranlib +# off til the end so many libraries can be made at once. +# +MAKELIB : .USE .PRECIOUS + ar $(ARFLAGS) $(.TARGET) $(.OODATE) +#ifndef no_ranlib +# ifdef many_libraries + ... +# endif /* many_libraries */ + ranlib $(.TARGET) +#endif /* no_ranlib */ + +#endif /* _MAKELIB_MK */ +.DE +.xH 2 On the Condition... +.Rd 1 +.LP +Like the C compiler before it, PMake allows you to configure the makefile, +based on the current environment, using conditional statements. A +conditional looks like this: +.DS +#if \fIboolean expression\fP +\fIlines\fP +#elif \fIanother boolean expression\fP +\fImore lines\fP +#else +\fIstill more lines\fP +#endif +.DE +They may be nested to a maximum depth of 30 and may occur anywhere +(except in a comment, of course). The +.CW # '' `` +must the very first character on the line. +.LP +Each +.I "boolean expression" +is made up of terms that look like function calls, the standard C +boolean operators +.CW && , +.CW || , +and +.CW ! , +and the standard relational operators +.CW == , +.CW != , +.CW > , +.CW >= , +.CW < , +and +.CW <= , +with +.CW == +and +.CW != +being overloaded to allow string comparisons as well. +.CW && +represents logical AND; +.CW || +is logical OR and +.CW ! +is logical NOT. The arithmetic and string operators take precedence +over all three of these operators, while NOT takes precedence over +AND, which takes precedence over OR. This precedence may be +overridden with parentheses, and an expression may be parenthesized to +your heart's content. Each term looks like a call on one of four +functions: +.nr pw 9 +.Ix 0 def make +.Ix 0 def conditional make +.Ix 0 def if make +.IP make \n(pw +The syntax is +.CW make( \fItarget\fP\c +.CW ) +where +.I target +is a target in the makefile. This is true if the given target was +specified on the command line, or as the source for a +.CW .MAIN +target (note that the sources for +.CW .MAIN +are only used if no targets were given on the command line). +.IP defined \n(pw +.Ix 0 def defined +.Ix 0 def conditional defined +.Ix 0 def if defined +The syntax is +.CW defined( \fIvariable\fP\c +.CW ) +and is true if +.I variable +is defined. Certain variables are defined in the system makefile that +identify the system on which PMake is being run. +.IP exists \n(pw +.Ix 0 def exists +.Ix 0 def conditional exists +.Ix 0 def if exists +The syntax is +.CW exists( \fIfile\fP\c +.CW ) +and is true if the file can be found on the global search path +(i.e. that defined by +.CW .PATH +targets, not by +.CW .PATH \fIsuffix\fP +targets). +.IP empty \n(pw +.Ix 0 def empty +.Ix 0 def conditional empty +.Ix 0 def if empty +This syntax is much like the others, except the string inside the +parentheses is of the same form as you would put between parentheses +when expanding a variable, complete with modifiers and everything. The +function returns true if the resulting string is empty (NOTE: an undefined +variable in this context will cause at the very least a warning +message about a malformed conditional, and at the worst will cause the +process to stop once it has read the makefile. If you want to check +for a variable being defined or empty, use the expression +.CW !defined( \fIvar\fP\c `` +.CW ") || empty(" \fIvar\fP\c +.CW ) '' +as the definition of +.CW || +will prevent the +.CW empty() +from being evaluated and causing an error, if the variable is +undefined). This can be used to see if a variable contains a given +word, for example: +.DS +#if !empty(\fIvar\fP:M\fIword\fP) +.DE +.LP +The arithmetic and string operators may only be used to test the value +of a variable. The lefthand side must contain the variable expansion, +while the righthand side contains either a string, enclosed in +double-quotes, or a number. The standard C numeric conventions (except +for specifying an octal number) apply to both sides. E.g. +.DS +#if $(OS) == 4.3 + +#if $(MACHINE) == "sun3" + +#if $(LOAD_ADDR) < 0xc000 +.DE +are all valid conditionals. In addition, the numeric value of a +variable can be tested as a boolean as follows: +.DS +#if $(LOAD) +.DE +would see if +.CW LOAD +contains a non-zero value and +.DS +#if !$(LOAD) +.DE +would test if +.CW LOAD +contains a zero value. +.LP +In addition to the bare +.CW #if ,'' `` +there are other forms that apply one of the first two functions to each +term. They are as follows: +.DS + ifdef \fRdefined\fP + ifndef \fR!defined\fP + ifmake \fRmake\fP + ifnmake \fR!make\fP +.DE +There are also the ``else if'' forms: +.CW elif , +.CW elifdef , +.CW elifndef , +.CW elifmake , +and +.CW elifnmake . +.LP +For instance, if you wish to create two versions of a program, one of which +is optimized (the production version) and the other of which is for debugging +(has symbols for dbx), you have two choices: you can create two +makefiles, one of which uses the +.CW \-g +flag for the compilation, while the other uses the +.CW \-O +flag, or you can use another target (call it +.CW debug ) +to create the debug version. The construct below will take care of +this for you. I have also made it so defining the variable +.CW DEBUG +(say with +.CW "pmake -D DEBUG" ) +will also cause the debug version to be made. +.DS +#if defined(DEBUG) || make(debug) +CFLAGS += -g +#else +CFLAGS += -O +#endif +.DE +There are, of course, problems with this approach. The most glaring +annoyance is that if you want to go from making a debug version to +making a production version, you have to remove all the object files, +or you will get some optimized and some debug versions in the same +program. Another annoyance is you have to be careful not to make two +targets that ``conflict'' because of some conditionals in the +makefile. For instance +.DS +#if make(print) +FORMATTER = ditroff -Plaser_printer +#endif +#if make(draft) +FORMATTER = nroff -Pdot_matrix_printer +#endif +.DE +would wreak havoc if you tried +.CW "pmake draft print" '' `` +since you would use the same formatter for each target. As I said, +this all gets somewhat complicated. +.xH 2 A Shell is a Shell is a Shell +.Rd 7 +.LP +In normal operation, the Bourne Shell (better known as +.CW sh '') `` +is used to execute the commands to re-create targets. PMake also allows you +to specify a different shell for it to use when executing these +commands. There are several things PMake must know about the shell you +wish to use. These things are specified as the sources for the +.CW .SHELL +.Ix 0 ref .SHELL +.Ix 0 ref target .SHELL +target by keyword, as follows: +.IP "\fBpath=\fP\fIpath\fP" +PMake needs to know where the shell actually resides, so it can +execute it. If you specify this and nothing else, PMake will use the +last component of the path and look in its table of the shells it +knows and use the specification it finds, if any. Use this if you just +want to use a different version of the Bourne or C Shell (yes, PMake knows +how to use the C Shell too). +.IP "\fBname=\fP\fIname\fP" +This is the name by which the shell is to be known. It is a single +word and, if no other keywords are specified (other than +.B path ), +it is the name by which PMake attempts to find a specification for +it (as mentioned above). You can use this if you would just rather use +the C Shell than the Bourne Shell +.CW ".SHELL: name=csh" '' (`` +will do it). +.IP "\fBquiet=\fP\fIecho-off command\fP" +As mentioned before, PMake actually controls whether commands are +printed by introducing commands into the shell's input stream. This +keyword, and the next two, control what those commands are. The +.B quiet +keyword is the command used to turn echoing off. Once it is turned +off, echoing is expected to remain off until the echo-on command is given. +.IP "\fBecho=\fP\fIecho-on command\fP" +The command PMake should give to turn echoing back on again. +.IP "\fBfilter=\fP\fIprinted echo-off command\fP" +Many shells will echo the echo-off command when it is given. This +keyword tells PMake in what format the shell actually prints the +echo-off command. Wherever PMake sees this string in the shell's +output, it will delete it and any following whitespace, up to and +including the next newline. See the example at the end of this section +for more details. +.IP "\fBechoFlag=\fP\fIflag to turn echoing on\fP" +Unless a target has been marked +.CW .SILENT , +PMake wants to start the shell running with echoing on. To do this, it +passes this flag to the shell as one of its arguments. If either this +or the next flag begins with a `\-', the flags will be passed to the +shell as separate arguments. Otherwise, the two will be concatenated +(if they are used at the same time, of course). +.IP "\fBerrFlag=\fP\fIflag to turn error checking on\fP" +Likewise, unless a target is marked +.CW .IGNORE , +PMake wishes error-checking to be on from the very start. To this end, +it will pass this flag to the shell as an argument. The same rules for +an initial `\-' apply as for the +.B echoFlag . +.IP "\fBcheck=\fP\fIcommand to turn error checking on\fP" +Just as for echo-control, error-control is achieved by inserting +commands into the shell's input stream. This is the command to make +the shell check for errors. It also serves another purpose if the +shell doesn't have error-control as commands, but I'll get into that +in a minute. Again, once error checking has been turned on, it is +expected to remain on until it is turned off again. +.IP "\fBignore=\fP\fIcommand to turn error checking off\fP" +This is the command PMake uses to turn error checking off. It has +another use if the shell doesn't do error-control, but I'll tell you +about that.\|.\|.\|now. +.IP "\fBhasErrCtl=\fP\fIyes or no\fP" +This takes a value that is either +.B yes +or +.B no . +Now you might think that the existence of the +.B check +and +.B ignore +keywords would be enough to tell PMake if the shell can do +error-control, but you'd be wrong. If +.B hasErrCtl +is +.B yes , +PMake uses the check and ignore commands in a straight-forward manner. +If this is +.B no , +however, their use is rather different. In this case, the check +command is used as a template, in which the string +.B %s +is replaced by the command that's about to be executed, to produce a +command for the shell that will echo the command to be executed. The +ignore command is also used as a template, again with +.B %s +replaced by the command to be executed, to produce a command that will +execute the command to be executed and ignore any error it returns. +When these strings are used as templates, you must provide newline(s) +.CW \en '') (`` +in the appropriate place(s). +.LP +The strings that follow these keywords may be enclosed in single or +double quotes (the quotes will be stripped off) and may contain the +usual C backslash-characters (\en is newline, \er is return, \eb is +backspace, \e' escapes a single-quote inside single-quotes, \e" +escapes a double-quote inside double-quotes). Now for an example. +.LP +This is actually the contents of the +.CW +system makefile, and causes PMake to use the Bourne Shell in such a +way that each command is printed as it is executed. That is, if more +than one command is given on a line, each will be printed separately. +Similarly, each time the body of a loop is executed, the commands +within that loop will be printed, etc. The specification runs like +this: +.DS +# +# This is a shell specification to have the Bourne shell echo +# the commands just before executing them, rather than when it reads +# them. Useful if you want to see how variables are being expanded, etc. +# +\&.SHELL : path=/bin/sh \e + quiet="set -" \e + echo="set -x" \e + filter="+ set - " \e + echoFlag=x \e + errFlag=e \e + hasErrCtl=yes \e + check="set -e" \e + ignore="set +e" +.DE +.LP +It tells PMake the following: +.Bp +The shell is located in the file +.CW /bin/sh . +It need not tell PMake that the name of the shell is +.CW sh +as PMake can figure that out for itself (it's the last component of +the path). +.Bp +The command to stop echoing is +.CW "set -" . +.Bp +The command to start echoing is +.CW "set -x" . +.Bp +When the echo off command is executed, the shell will print +.CW "+ set - " +(The `+' comes from using the +.CW \-x +flag (rather than the +.CW \-v +flag PMake usually uses)). PMake will remove all occurrences of this +string from the output, so you don't notice extra commands you didn't +put there. +.Bp +The flag the Bourne Shell will take to start echoing in this way is +the +.CW \-x +flag. The Bourne Shell will only take its flag arguments concatenated +as its first argument, so neither this nor the +.B errFlag +specification begins with a \-. +.Bp +The flag to use to turn error-checking on from the start is +.CW \-e . +.Bp +The shell can turn error-checking on and off, and the commands to do +so are +.CW "set +e" +and +.CW "set -e" , +respectively. +.LP +I should note that this specification is for Bourne Shells that are +not part of Berkeley +.UX , +as shells from Berkeley don't do error control. You can get a similar +effect, however, by changing the last three lines to be: +.DS + hasErrCtl=no \e + check="echo \e"+ %s\e"\en" \e + ignore="sh -c '%s || exit 0\en" +.DE +.LP +This will cause PMake to execute the two commands +.DS +echo "+ \fIcmd\fP" +sh -c '\fIcmd\fP || true' +.DE +for each command for which errors are to be ignored. (In case you are +wondering, the thing for +.CW ignore +tells the shell to execute another shell without error checking on and +always exit 0, since the +.B || +causes the +.CW "exit 0" +to be executed only if the first command exited non-zero, and if the +first command exited zero, the shell will also exit zero, since that's +the last command it executed). +.xH 2 Compatibility +.Ix 0 ref compatibility +.LP +There are three (well, 3 \(12) levels of backwards-compatibility built +into PMake. Most makefiles will need none at all. Some may need a +little bit of work to operate correctly when run in parallel. Each +level encompasses the previous levels (e.g. +.B \-B +(one shell per command) implies +.B \-V ) +The three levels are described in the following three sections. +.xH 3 DEFCON 3 \*- Variable Expansion +.Ix 0 ref compatibility +.LP +As noted before, PMake will not expand a variable unless it knows of a +value for it. This can cause problems for makefiles that expect to +leave variables undefined except in special circumstances (e.g. if +more flags need to be passed to the C compiler or the output from a +text processor should be sent to a different printer). If the +variables are enclosed in curly braces +.CW ${PRINTER} ''), (`` +the shell will let them pass. If they are enclosed in parentheses, +however, the shell will declare a syntax error and the make will come +to a grinding halt. +.LP +You have two choices: change the makefile to define the variables +(their values can be overridden on the command line, since that's +where they would have been set if you used Make, anyway) or always give the +.B \-V +flag (this can be done with the +.CW .MAKEFLAGS +target, if you want). +.xH 3 DEFCON 2 \*- The Number of the Beast +.Ix 0 ref compatibility +.LP +Then there are the makefiles that expect certain commands, such as +changing to a different directory, to not affect other commands in a +target's creation script. You can solve this is either by going +back to executing one shell per command (which is what the +.B \-B +flag forces PMake to do), which slows the process down a good bit and +requires you to use semicolons and escaped newlines for shell constructs, or +by changing the makefile to execute the offending command(s) in a subshell +(by placing the line inside parentheses), like so: +.DS +install :: .MAKE + (cd src; $(.PMAKE) install) + (cd lib; $(.PMAKE) install) + (cd man; $(.PMAKE) install) +.DE +.Ix 0 ref operator double-colon +.Ix 0 ref variable global .PMAKE +.Ix 0 ref .PMAKE +.Ix 0 ref .MAKE +.Ix 0 ref attribute .MAKE +This will always execute the three makes (even if the +.B \-n +flag was given) because of the combination of the ``::'' operator and +the +.CW .MAKE +attribute. Each command will change to the proper directory to perform +the install, leaving the main shell in the directory in which it started. +.xH 3 "DEFCON 1 \*- Imitation is the Not the Highest Form of Flattery" +.Ix 0 ref compatibility +.LP +The final category of makefile is the one where every command requires +input, the dependencies are incompletely specified, or you simply +cannot create more than one target at a time, as mentioned earlier. In +addition, you may not have the time or desire to upgrade the makefile +to run smoothly with PMake. If you are the conservative sort, this is +the compatibility mode for you. It is entered either by giving PMake +the +.B \-M +flag (for Make), or by executing PMake as +.CW make .'' `` +In either case, PMake performs things exactly like Make (while still +supporting most of the nice new features PMake provides). This +includes: +.IP \(bu 2 +No parallel execution. +.IP \(bu 2 +Targets are made in the exact order specified by the makefile. The +sources for each target are made in strict left-to-right order, etc. +.IP \(bu 2 +A single Bourne shell is used to execute each command, thus the +shell's +.CW $$ +variable is useless, changing directories doesn't work across command +lines, etc. +.IP \(bu 2 +If no special characters exist in a command line, PMake will break the +command into words itself and execute the command directly, without +executing a shell first. The characters that cause PMake to execute a +shell are: +.CW # , +.CW = , +.CW | , +.CW ^ , +.CW ( , +.CW ) , +.CW { , +.CW } , +.CW ; , +.CW & , +.CW < , +.CW > , +.CW * , +.CW ? , +.CW [ , +.CW ] , +.CW : , +.CW $ , +.CW ` , +and +.CW \e . +You should notice that these are all the characters that are given +special meaning by the shell (except +.CW ' +and +.CW " , +which PMake deals with all by its lonesome). +.IP \(bu 2 +The use of the null suffix is turned off. +.Ix 0 ref "null suffix" +.Ix 0 ref suffix null +.xH 2 The Way Things Work +.LP +When PMake reads the makefile, it parses sources and targets into +nodes in a graph. The graph is directed only in the sense that PMake +knows which way is up. Each node contains not only links to all its +parents and children (the nodes that depend on it and those on which +it depends, respectively), but also a count of the number of its +children that have already been processed. +.LP +The most important thing to know about how PMake uses this graph is +that the traversal is breadth-first and occurs in two passes. +.LP +After PMake has parsed the makefile, it begins with the nodes the user +has told it to make (either on the command line, or via a +.CW .MAIN +target, or by the target being the first in the file not labeled with +the +.CW .NOTMAIN +attribute) placed in a queue. It continues to take the node off the +front of the queue, mark it as something that needs to be made, pass +the node to +.CW Suff_FindDeps +(mentioned earlier) to find any implicit sources for the node, and +place all the node's children that have yet to be marked at the end of +the queue. If any of the children is a +.CW .USE +rule, its attributes are applied to the parent, then its commands are +appended to the parent's list of commands and its children are linked +to its parent. The parent's unmade children counter is then decremented +(since the +.CW .USE +node has been processed). You will note that this allows a +.CW .USE +node to have children that are +.CW .USE +nodes and the rules will be applied in sequence. +If the node has no children, it is placed at the end of +another queue to be examined in the second pass. This process +continues until the first queue is empty. +.LP +At this point, all the leaves of the graph are in the examination +queue. PMake removes the node at the head of the queue and sees if it +is out-of-date. If it is, it is passed to a function that will execute +the commands for the node asynchronously. When the commands have +completed, all the node's parents have their unmade children counter +decremented and, if the counter is then 0, they are placed on the +examination queue. Likewise, if the node is up-to-date. Only those +parents that were marked on the downward pass are processed in this +way. Thus PMake traverses the graph back up to the nodes the user +instructed it to create. When the examination queue is empty and no +shells are running to create a target, PMake is finished. +.LP +Once all targets have been processed, PMake executes the commands +attached to the +.CW .END +target, either explicitly or through the use of an ellipsis in a shell +script. If there were no errors during the entire process but there +are still some targets unmade (PMake keeps a running count of how many +targets are left to be made), there is a cycle in the graph. PMake does +a depth-first traversal of the graph to find all the targets that +weren't made and prints them out one by one. +.xH 1 Answers to Exercises +.IP (3.1) +This is something of a trick question, for which I apologize. The +trick comes from the UNIX definition of a suffix, which PMake doesn't +necessarily share. You will have noticed that all the suffixes used in +this tutorial (and in UNIX in general) begin with a period +.CW .ms , ( +.CW .c , +etc.). Now, PMake's idea of a suffix is more like English's: it's the +characters at the end of a word. With this in mind, one possible +.Ix 0 def suffix +solution to this problem goes as follows: +.DS I +\&.SUFFIXES : ec.exe .exe ec.obj .obj .asm +ec.objec.exe .obj.exe : + link -o $(.TARGET) $(.IMPSRC) +\&.asmec.obj : + asm -o $(.TARGET) -DDO_ERROR_CHECKING $(.IMPSRC) +\&.asm.obj : + asm -o $(.TARGET) $(.IMPSRC) +.DE +.IP (3.2) +The trick to this one lies in the ``:='' variable-assignment operator +and the ``:S'' variable-expansion modifier. +.Ix 0 ref variable assignment expanded +.Ix 0 ref variable expansion modified +.Ix 0 ref modifier substitute +.Ix 0 ref :S +.Ix 0 ref := +Basically what you want is to take the pointer variable, so to speak, +and transform it into an invocation of the variable at which it +points. You might try something like +.DS I +$(PTR:S/^/\e$(/:S/$/)) +.DE +which places +.CW $( '' `` +at the front of the variable name and +.CW ) '' `` +at the end, thus transforming +.CW VAR ,'' `` +for example, into +.CW $(VAR) ,'' `` +which is just what we want. Unfortunately (as you know if you've tried +it), since, as it says in the hint, PMake does no further substitution +on the result of a modified expansion, that's \fIall\fP you get. The +solution is to make use of ``:='' to place that string into yet +another variable, then invoke the other variable directly: +.DS I +*PTR := $(PTR:S/^/\e$(/:S/$/)/) +.DE +You can then use +.CW $(*PTR) '' `` +to your heart's content. +.de Gp +.XP +\&\fB\\$1:\fP +.. +.xH 1 Glossary of Jargon +.Gp "attribute" +A property given to a target that causes PMake to treat it differently. +.Gp "command script" +The lines immediately following a dependency line that specify +commands to execute to create each of the targets on the dependency +line. Each line in the command script must begin with a tab. +.Gp "command-line variable" +A variable defined in an argument when PMake is first executed. +Overrides all assignments to the same variable name in the makefile. +.Gp "conditional" +A construct much like that used in C that allows a makefile to be +configured on the fly based on the local environment, or on what is being +made by that invocation of PMake. +.Gp "creation script" +Commands used to create a target. See ``command script.'' +.Gp "dependency" +The relationship between a source and a target. This comes in three +flavors, as indicated by the operator between the target and the +source. `:' gives a straight time-wise dependency (if the target is +older than the source, the target is out-of-date), while `!' provides +simply an ordering and always considers the target out-of-date. `::' +is much like `:', save it creates multiple instances of a target each +of which depends on its own list of sources. +.Gp "dynamic source" +This refers to a source that has a local variable invocation in it. It +allows a single dependency line to specify a different source for each +target on the line. +.Gp "global variable" +Any variable defined in a makefile. Takes precedence over variables +defined in the environment, but not over command-line or local variables. +.Gp "input graph" +What PMake constructs from a makefile. Consists of nodes made of the +targets in the makefile, and the links between them (the +dependencies). The links are directed (from source to target) and +there may not be any cycles (loops) in the graph. +.Gp "local variable" +A variable defined by PMake visible only in a target's shell script. +There are seven local variables, not all of which are defined for +every target: +.CW .TARGET , +.CW .ALLSRC , +.CW .OODATE , +.CW .PREFIX , +.CW .IMPSRC , +.CW .ARCHIVE , +and +.CW .MEMBER . +.CW .TARGET , +.CW .PREFIX , +.CW .ARCHIVE , +and +.CW .MEMBER +may be used on dependency lines to create ``dynamic sources.'' +.Gp "makefile" +A file that describes how a system is built. If you don't know what it +is after reading this tutorial.\|.\|.\|. +.Gp "modifier" +A letter, following a colon, used to alter how a variable is expanded. +It has no effect on the variable itself. +.Gp "operator" +What separates a source from a target (on a dependency line) and specifies +the relationship between the two. There are three: +.CW : ', ` +.CW :: ', ` +and +.CW ! '. ` +.Gp "search path" +A list of directories in which a file should be sought. PMake's view +of the contents of directories in a search path does not change once +the makefile has been read. A file is sought on a search path only if +it is exclusively a source. +.Gp "shell" +A program to which commands are passed in order to create targets. +.Gp "source" +Anything to the right of an operator on a dependency line. Targets on +the dependency line are usually created from the sources. +.Gp "special target" +A target that causes PMake to do special things when it's encountered. +.Gp "suffix" +The tail end of a file name. Usually begins with a period, +.CW .c +or +.CW .ms , +e.g. +.Gp "target" +A word to the left of the operator on a dependency line. More +generally, any file that PMake might create. A file may be (and often +is) both a target and a source (what it is depends on how PMake is +looking at it at the time \*- sort of like the wave/particle duality +of light, you know). +.Gp "transformation rule" +A special construct in a makefile that specifies how to create a file +of one type from a file of another, as indicated by their suffixes. +.Gp "variable expansion" +The process of substituting the value of a variable for a reference to +it. Expansion may be altered by means of modifiers. +.Gp "variable" +A place in which to store text that may be retrieved later. Also used +to define the local environment. Conditionals exist that test whether +a variable is defined or not. +.bp +.\" Output table of contents last, with an entry for the index, making +.\" sure to save and restore the last real page number for the index... +.nr @n \n(PN+1 +.\" We are not generating an index +.\" .XS \n(@n +.\" Index +.\" .XE +.nr %% \n% +.PX +.nr % \n(%% diff --git a/usr.bin/make/arch.c b/usr.bin/make/arch.c new file mode 100644 index 0000000..8241434 --- /dev/null +++ b/usr.bin/make/arch.c @@ -0,0 +1,1369 @@ +/* $NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)arch.c 8.2 (Berkeley) 1/2/94"; +#else +__RCSID("$NetBSD: arch.c,v 1.70 2017/04/16 20:49:09 riastradh Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * arch.c -- + * Functions to manipulate libraries, archives and their members. + * + * Once again, cacheing/hashing comes into play in the manipulation + * of archives. The first time an archive is referenced, all of its members' + * headers are read and hashed and the archive closed again. All hashed + * archives are kept on a list which is searched each time an archive member + * is referenced. + * + * The interface to this module is: + * Arch_ParseArchive Given an archive specification, return a list + * of GNode's, one for each member in the spec. + * FAILURE is returned if the specification is + * invalid for some reason. + * + * Arch_Touch Alter the modification time of the archive + * member described by the given node to be + * the current time. + * + * Arch_TouchLib Update the modification time of the library + * described by the given node. This is special + * because it also updates the modification time + * of the library's table of contents. + * + * Arch_MTime Find the modification time of a member of + * an archive *in the archive*. The time is also + * placed in the member's GNode. Returns the + * modification time. + * + * Arch_MemTime Find the modification time of a member of + * an archive. Called when the member doesn't + * already exist. Looks in the archive for the + * modification time. Returns the modification + * time. + * + * Arch_FindLib Search for a library along a path. The + * library name in the GNode should be in + * -l format. + * + * Arch_LibOODate Special function to decide if a library node + * is out-of-date. + * + * Arch_Init Initialize this module. + * + * Arch_End Cleanup this module. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "config.h" + +#ifdef TARGET_MACHINE +#undef MAKE_MACHINE +#define MAKE_MACHINE TARGET_MACHINE +#endif +#ifdef TARGET_MACHINE_ARCH +#undef MAKE_MACHINE_ARCH +#define MAKE_MACHINE_ARCH TARGET_MACHINE_ARCH +#endif + +static Lst archives; /* Lst of archives we've already examined */ + +typedef struct Arch { + char *name; /* Name of archive */ + Hash_Table members; /* All the members of the archive described + * by key/value pairs */ + char *fnametab; /* Extended name table strings */ + size_t fnamesize; /* Size of the string table */ +} Arch; + +static int ArchFindArchive(const void *, const void *); +#ifdef CLEANUP +static void ArchFree(void *); +#endif +static struct ar_hdr *ArchStatMember(char *, char *, Boolean); +static FILE *ArchFindMember(char *, char *, struct ar_hdr *, const char *); +#if defined(__svr4__) || defined(__SVR4) || defined(__ELF__) +#define SVR4ARCHIVES +static int ArchSVR4Entry(Arch *, char *, size_t, FILE *); +#endif + +#ifdef CLEANUP +/*- + *----------------------------------------------------------------------- + * ArchFree -- + * Free memory used by an archive + * + * Results: + * None. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static void +ArchFree(void *ap) +{ + Arch *a = (Arch *)ap; + Hash_Search search; + Hash_Entry *entry; + + /* Free memory from hash entries */ + for (entry = Hash_EnumFirst(&a->members, &search); + entry != NULL; + entry = Hash_EnumNext(&search)) + free(Hash_GetValue(entry)); + + free(a->name); + free(a->fnametab); + Hash_DeleteTable(&a->members); + free(a); +} +#endif + + + +/*- + *----------------------------------------------------------------------- + * Arch_ParseArchive -- + * Parse the archive specification in the given line and find/create + * the nodes for the specified archive members, placing their nodes + * on the given list. + * + * Input: + * linePtr Pointer to start of specification + * nodeLst Lst on which to place the nodes + * ctxt Context in which to expand variables + * + * Results: + * SUCCESS if it was a valid specification. The linePtr is updated + * to point to the first non-space after the archive spec. The + * nodes for the members are placed on the given list. + * + * Side Effects: + * Some nodes may be created. The given list is extended. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Arch_ParseArchive(char **linePtr, Lst nodeLst, GNode *ctxt) +{ + char *cp; /* Pointer into line */ + GNode *gn; /* New node */ + char *libName; /* Library-part of specification */ + char *memName; /* Member-part of specification */ + char *nameBuf; /* temporary place for node name */ + char saveChar; /* Ending delimiter of member-name */ + Boolean subLibName; /* TRUE if libName should have/had + * variable substitution performed on it */ + + libName = *linePtr; + + subLibName = FALSE; + + for (cp = libName; *cp != '(' && *cp != '\0'; cp++) { + if (*cp == '$') { + /* + * Variable spec, so call the Var module to parse the puppy + * so we can safely advance beyond it... + */ + int length; + void *freeIt; + char *result; + + result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES, + &length, &freeIt); + free(freeIt); + + if (result == var_Error) { + return(FAILURE); + } else { + subLibName = TRUE; + } + + cp += length-1; + } + } + + *cp++ = '\0'; + if (subLibName) { + libName = Var_Subst(NULL, libName, ctxt, VARF_UNDEFERR|VARF_WANTRES); + } + + + for (;;) { + /* + * First skip to the start of the member's name, mark that + * place and skip to the end of it (either white-space or + * a close paren). + */ + Boolean doSubst = FALSE; /* TRUE if need to substitute in memName */ + + while (*cp != '\0' && *cp != ')' && isspace ((unsigned char)*cp)) { + cp++; + } + memName = cp; + while (*cp != '\0' && *cp != ')' && !isspace ((unsigned char)*cp)) { + if (*cp == '$') { + /* + * Variable spec, so call the Var module to parse the puppy + * so we can safely advance beyond it... + */ + int length; + void *freeIt; + char *result; + + result = Var_Parse(cp, ctxt, VARF_UNDEFERR|VARF_WANTRES, + &length, &freeIt); + free(freeIt); + + if (result == var_Error) { + return(FAILURE); + } else { + doSubst = TRUE; + } + + cp += length; + } else { + cp++; + } + } + + /* + * If the specification ends without a closing parenthesis, + * chances are there's something wrong (like a missing backslash), + * so it's better to return failure than allow such things to happen + */ + if (*cp == '\0') { + printf("No closing parenthesis in archive specification\n"); + return (FAILURE); + } + + /* + * If we didn't move anywhere, we must be done + */ + if (cp == memName) { + break; + } + + saveChar = *cp; + *cp = '\0'; + + /* + * XXX: This should be taken care of intelligently by + * SuffExpandChildren, both for the archive and the member portions. + */ + /* + * If member contains variables, try and substitute for them. + * This will slow down archive specs with dynamic sources, of course, + * since we'll be (non-)substituting them three times, but them's + * the breaks -- we need to do this since SuffExpandChildren calls + * us, otherwise we could assume the thing would be taken care of + * later. + */ + if (doSubst) { + char *buf; + char *sacrifice; + char *oldMemName = memName; + size_t sz; + + memName = Var_Subst(NULL, memName, ctxt, + VARF_UNDEFERR|VARF_WANTRES); + + /* + * Now form an archive spec and recurse to deal with nested + * variables and multi-word variable values.... The results + * are just placed at the end of the nodeLst we're returning. + */ + sz = strlen(memName)+strlen(libName)+3; + buf = sacrifice = bmake_malloc(sz); + + snprintf(buf, sz, "%s(%s)", libName, memName); + + if (strchr(memName, '$') && strcmp(memName, oldMemName) == 0) { + /* + * Must contain dynamic sources, so we can't deal with it now. + * Just create an ARCHV node for the thing and let + * SuffExpandChildren handle it... + */ + gn = Targ_FindNode(buf, TARG_CREATE); + + if (gn == NULL) { + free(buf); + return(FAILURE); + } else { + gn->type |= OP_ARCHV; + (void)Lst_AtEnd(nodeLst, gn); + } + } else if (Arch_ParseArchive(&sacrifice, nodeLst, ctxt)!=SUCCESS) { + /* + * Error in nested call -- free buffer and return FAILURE + * ourselves. + */ + free(buf); + return(FAILURE); + } + /* + * Free buffer and continue with our work. + */ + free(buf); + } else if (Dir_HasWildcards(memName)) { + Lst members = Lst_Init(FALSE); + char *member; + size_t sz = MAXPATHLEN, nsz; + nameBuf = bmake_malloc(sz); + + Dir_Expand(memName, dirSearchPath, members); + while (!Lst_IsEmpty(members)) { + member = (char *)Lst_DeQueue(members); + nsz = strlen(libName) + strlen(member) + 3; + if (sz > nsz) + nameBuf = bmake_realloc(nameBuf, sz = nsz * 2); + + snprintf(nameBuf, sz, "%s(%s)", libName, member); + free(member); + gn = Targ_FindNode(nameBuf, TARG_CREATE); + if (gn == NULL) { + free(nameBuf); + return (FAILURE); + } else { + /* + * We've found the node, but have to make sure the rest of + * the world knows it's an archive member, without having + * to constantly check for parentheses, so we type the + * thing with the OP_ARCHV bit before we place it on the + * end of the provided list. + */ + gn->type |= OP_ARCHV; + (void)Lst_AtEnd(nodeLst, gn); + } + } + Lst_Destroy(members, NULL); + free(nameBuf); + } else { + size_t sz = strlen(libName) + strlen(memName) + 3; + nameBuf = bmake_malloc(sz); + snprintf(nameBuf, sz, "%s(%s)", libName, memName); + gn = Targ_FindNode(nameBuf, TARG_CREATE); + free(nameBuf); + if (gn == NULL) { + return (FAILURE); + } else { + /* + * We've found the node, but have to make sure the rest of the + * world knows it's an archive member, without having to + * constantly check for parentheses, so we type the thing with + * the OP_ARCHV bit before we place it on the end of the + * provided list. + */ + gn->type |= OP_ARCHV; + (void)Lst_AtEnd(nodeLst, gn); + } + } + if (doSubst) { + free(memName); + } + + *cp = saveChar; + } + + /* + * If substituted libName, free it now, since we need it no longer. + */ + if (subLibName) { + free(libName); + } + + /* + * We promised the pointer would be set up at the next non-space, so + * we must advance cp there before setting *linePtr... (note that on + * entrance to the loop, cp is guaranteed to point at a ')') + */ + do { + cp++; + } while (*cp != '\0' && isspace ((unsigned char)*cp)); + + *linePtr = cp; + return (SUCCESS); +} + +/*- + *----------------------------------------------------------------------- + * ArchFindArchive -- + * See if the given archive is the one we are looking for. Called + * From ArchStatMember and ArchFindMember via Lst_Find. + * + * Input: + * ar Current list element + * archName Name we want + * + * Results: + * 0 if it is, non-zero if it isn't. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static int +ArchFindArchive(const void *ar, const void *archName) +{ + return (strcmp(archName, ((const Arch *)ar)->name)); +} + +/*- + *----------------------------------------------------------------------- + * ArchStatMember -- + * Locate a member of an archive, given the path of the archive and + * the path of the desired member. + * + * Input: + * archive Path to the archive + * member Name of member. If it is a path, only the last + * component is used. + * hash TRUE if archive should be hashed if not already so. + * + * Results: + * A pointer to the current struct ar_hdr structure for the member. Note + * That no position is returned, so this is not useful for touching + * archive members. This is mostly because we have no assurances that + * The archive will remain constant after we read all the headers, so + * there's not much point in remembering the position... + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +static struct ar_hdr * +ArchStatMember(char *archive, char *member, Boolean hash) +{ +#define AR_MAX_NAME_LEN (sizeof(arh.ar_name)-1) + FILE * arch; /* Stream to archive */ + int size; /* Size of archive member */ + char *cp; /* Useful character pointer */ + char magic[SARMAG]; + LstNode ln; /* Lst member containing archive descriptor */ + Arch *ar; /* Archive descriptor */ + Hash_Entry *he; /* Entry containing member's description */ + struct ar_hdr arh; /* archive-member header for reading archive */ + char memName[MAXPATHLEN+1]; + /* Current member name while hashing. */ + + /* + * Because of space constraints and similar things, files are archived + * using their final path components, not the entire thing, so we need + * to point 'member' to the final component, if there is one, to make + * the comparisons easier... + */ + cp = strrchr(member, '/'); + if (cp != NULL) { + member = cp + 1; + } + + ln = Lst_Find(archives, archive, ArchFindArchive); + if (ln != NULL) { + ar = (Arch *)Lst_Datum(ln); + + he = Hash_FindEntry(&ar->members, member); + + if (he != NULL) { + return ((struct ar_hdr *)Hash_GetValue(he)); + } else { + /* Try truncated name */ + char copy[AR_MAX_NAME_LEN+1]; + size_t len = strlen(member); + + if (len > AR_MAX_NAME_LEN) { + len = AR_MAX_NAME_LEN; + strncpy(copy, member, AR_MAX_NAME_LEN); + copy[AR_MAX_NAME_LEN] = '\0'; + } + if ((he = Hash_FindEntry(&ar->members, copy)) != NULL) + return ((struct ar_hdr *)Hash_GetValue(he)); + return NULL; + } + } + + if (!hash) { + /* + * Caller doesn't want the thing hashed, just use ArchFindMember + * to read the header for the member out and close down the stream + * again. Since the archive is not to be hashed, we assume there's + * no need to allocate extra room for the header we're returning, + * so just declare it static. + */ + static struct ar_hdr sarh; + + arch = ArchFindMember(archive, member, &sarh, "r"); + + if (arch == NULL) { + return NULL; + } else { + fclose(arch); + return (&sarh); + } + } + + /* + * We don't have this archive on the list yet, so we want to find out + * everything that's in it and cache it so we can get at it quickly. + */ + arch = fopen(archive, "r"); + if (arch == NULL) { + return NULL; + } + + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if ((fread(magic, SARMAG, 1, arch) != 1) || + (strncmp(magic, ARMAG, SARMAG) != 0)) { + fclose(arch); + return NULL; + } + + ar = bmake_malloc(sizeof(Arch)); + ar->name = bmake_strdup(archive); + ar->fnametab = NULL; + ar->fnamesize = 0; + Hash_InitTable(&ar->members, -1); + memName[AR_MAX_NAME_LEN] = '\0'; + + while (fread((char *)&arh, sizeof(struct ar_hdr), 1, arch) == 1) { + if (strncmp( arh.ar_fmag, ARFMAG, sizeof(arh.ar_fmag)) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + goto badarch; + } else { + /* + * We need to advance the stream's pointer to the start of the + * next header. Files are padded with newlines to an even-byte + * boundary, so we need to extract the size of the file from the + * 'size' field of the header and round it up during the seek. + */ + arh.ar_size[sizeof(arh.ar_size)-1] = '\0'; + size = (int)strtol(arh.ar_size, NULL, 10); + + (void)strncpy(memName, arh.ar_name, sizeof(arh.ar_name)); + for (cp = &memName[AR_MAX_NAME_LEN]; *cp == ' '; cp--) { + continue; + } + cp[1] = '\0'; + +#ifdef SVR4ARCHIVES + /* + * svr4 names are slash terminated. Also svr4 extended AR format. + */ + if (memName[0] == '/') { + /* + * svr4 magic mode; handle it + */ + switch (ArchSVR4Entry(ar, memName, size, arch)) { + case -1: /* Invalid data */ + goto badarch; + case 0: /* List of files entry */ + continue; + default: /* Got the entry */ + break; + } + } + else { + if (cp[0] == '/') + cp[0] = '\0'; + } +#endif + +#ifdef AR_EFMT1 + /* + * BSD 4.4 extended AR format: #1/, with name as the + * first bytes of the file + */ + if (strncmp(memName, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0 && + isdigit((unsigned char)memName[sizeof(AR_EFMT1) - 1])) { + + unsigned int elen = atoi(&memName[sizeof(AR_EFMT1)-1]); + + if (elen > MAXPATHLEN) + goto badarch; + if (fread(memName, elen, 1, arch) != 1) + goto badarch; + memName[elen] = '\0'; + if (fseek(arch, -elen, SEEK_CUR) != 0) + goto badarch; + if (DEBUG(ARCH) || DEBUG(MAKE)) { + fprintf(debug_file, "ArchStat: Extended format entry for %s\n", memName); + } + } +#endif + + he = Hash_CreateEntry(&ar->members, memName, NULL); + Hash_SetValue(he, bmake_malloc(sizeof(struct ar_hdr))); + memcpy(Hash_GetValue(he), &arh, sizeof(struct ar_hdr)); + } + if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) + goto badarch; + } + + fclose(arch); + + (void)Lst_AtEnd(archives, ar); + + /* + * Now that the archive has been read and cached, we can look into + * the hash table to find the desired member's header. + */ + he = Hash_FindEntry(&ar->members, member); + + if (he != NULL) { + return ((struct ar_hdr *)Hash_GetValue(he)); + } else { + return NULL; + } + +badarch: + fclose(arch); + Hash_DeleteTable(&ar->members); + free(ar->fnametab); + free(ar); + return NULL; +} + +#ifdef SVR4ARCHIVES +/*- + *----------------------------------------------------------------------- + * ArchSVR4Entry -- + * Parse an SVR4 style entry that begins with a slash. + * If it is "//", then load the table of filenames + * If it is "/", then try to substitute the long file name + * from offset of a table previously read. + * + * Results: + * -1: Bad data in archive + * 0: A table was loaded from the file + * 1: Name was successfully substituted from table + * 2: Name was not successfully substituted from table + * + * Side Effects: + * If a table is read, the file pointer is moved to the next archive + * member + * + *----------------------------------------------------------------------- + */ +static int +ArchSVR4Entry(Arch *ar, char *name, size_t size, FILE *arch) +{ +#define ARLONGNAMES1 "//" +#define ARLONGNAMES2 "/ARFILENAMES" + size_t entry; + char *ptr, *eptr; + + if (strncmp(name, ARLONGNAMES1, sizeof(ARLONGNAMES1) - 1) == 0 || + strncmp(name, ARLONGNAMES2, sizeof(ARLONGNAMES2) - 1) == 0) { + + if (ar->fnametab != NULL) { + if (DEBUG(ARCH)) { + fprintf(debug_file, "Attempted to redefine an SVR4 name table\n"); + } + return -1; + } + + /* + * This is a table of archive names, so we build one for + * ourselves + */ + ar->fnametab = bmake_malloc(size); + ar->fnamesize = size; + + if (fread(ar->fnametab, size, 1, arch) != 1) { + if (DEBUG(ARCH)) { + fprintf(debug_file, "Reading an SVR4 name table failed\n"); + } + return -1; + } + eptr = ar->fnametab + size; + for (entry = 0, ptr = ar->fnametab; ptr < eptr; ptr++) + switch (*ptr) { + case '/': + entry++; + *ptr = '\0'; + break; + + case '\n': + break; + + default: + break; + } + if (DEBUG(ARCH)) { + fprintf(debug_file, "Found svr4 archive name table with %lu entries\n", + (unsigned long)entry); + } + return 0; + } + + if (name[1] == ' ' || name[1] == '\0') + return 2; + + entry = (size_t)strtol(&name[1], &eptr, 0); + if ((*eptr != ' ' && *eptr != '\0') || eptr == &name[1]) { + if (DEBUG(ARCH)) { + fprintf(debug_file, "Could not parse SVR4 name %s\n", name); + } + return 2; + } + if (entry >= ar->fnamesize) { + if (DEBUG(ARCH)) { + fprintf(debug_file, "SVR4 entry offset %s is greater than %lu\n", + name, (unsigned long)ar->fnamesize); + } + return 2; + } + + if (DEBUG(ARCH)) { + fprintf(debug_file, "Replaced %s with %s\n", name, &ar->fnametab[entry]); + } + + (void)strncpy(name, &ar->fnametab[entry], MAXPATHLEN); + name[MAXPATHLEN] = '\0'; + return 1; +} +#endif + + +/*- + *----------------------------------------------------------------------- + * ArchFindMember -- + * Locate a member of an archive, given the path of the archive and + * the path of the desired member. If the archive is to be modified, + * the mode should be "r+", if not, it should be "r". + * + * Input: + * archive Path to the archive + * member Name of member. If it is a path, only the last + * component is used. + * arhPtr Pointer to header structure to be filled in + * mode The mode for opening the stream + * + * Results: + * An FILE *, opened for reading and writing, positioned at the + * start of the member's struct ar_hdr, or NULL if the member was + * nonexistent. The current struct ar_hdr for member. + * + * Side Effects: + * The passed struct ar_hdr structure is filled in. + * + *----------------------------------------------------------------------- + */ +static FILE * +ArchFindMember(char *archive, char *member, struct ar_hdr *arhPtr, + const char *mode) +{ + FILE * arch; /* Stream to archive */ + int size; /* Size of archive member */ + char *cp; /* Useful character pointer */ + char magic[SARMAG]; + size_t len, tlen; + + arch = fopen(archive, mode); + if (arch == NULL) { + return NULL; + } + + /* + * We use the ARMAG string to make sure this is an archive we + * can handle... + */ + if ((fread(magic, SARMAG, 1, arch) != 1) || + (strncmp(magic, ARMAG, SARMAG) != 0)) { + fclose(arch); + return NULL; + } + + /* + * Because of space constraints and similar things, files are archived + * using their final path components, not the entire thing, so we need + * to point 'member' to the final component, if there is one, to make + * the comparisons easier... + */ + cp = strrchr(member, '/'); + if (cp != NULL) { + member = cp + 1; + } + len = tlen = strlen(member); + if (len > sizeof(arhPtr->ar_name)) { + tlen = sizeof(arhPtr->ar_name); + } + + while (fread((char *)arhPtr, sizeof(struct ar_hdr), 1, arch) == 1) { + if (strncmp(arhPtr->ar_fmag, ARFMAG, sizeof(arhPtr->ar_fmag) ) != 0) { + /* + * The header is bogus, so the archive is bad + * and there's no way we can recover... + */ + fclose(arch); + return NULL; + } else if (strncmp(member, arhPtr->ar_name, tlen) == 0) { + /* + * If the member's name doesn't take up the entire 'name' field, + * we have to be careful of matching prefixes. Names are space- + * padded to the right, so if the character in 'name' at the end + * of the matched string is anything but a space, this isn't the + * member we sought. + */ + if (tlen != sizeof(arhPtr->ar_name) && arhPtr->ar_name[tlen] != ' '){ + goto skip; + } else { + /* + * To make life easier, we reposition the file at the start + * of the header we just read before we return the stream. + * In a more general situation, it might be better to leave + * the file at the actual member, rather than its header, but + * not here... + */ + if (fseek(arch, -sizeof(struct ar_hdr), SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + return (arch); + } + } else +#ifdef AR_EFMT1 + /* + * BSD 4.4 extended AR format: #1/, with name as the + * first bytes of the file + */ + if (strncmp(arhPtr->ar_name, AR_EFMT1, + sizeof(AR_EFMT1) - 1) == 0 && + isdigit((unsigned char)arhPtr->ar_name[sizeof(AR_EFMT1) - 1])) { + + unsigned int elen = atoi(&arhPtr->ar_name[sizeof(AR_EFMT1)-1]); + char ename[MAXPATHLEN + 1]; + + if (elen > MAXPATHLEN) { + fclose(arch); + return NULL; + } + if (fread(ename, elen, 1, arch) != 1) { + fclose(arch); + return NULL; + } + ename[elen] = '\0'; + if (DEBUG(ARCH) || DEBUG(MAKE)) { + fprintf(debug_file, "ArchFind: Extended format entry for %s\n", ename); + } + if (strncmp(ename, member, len) == 0) { + /* Found as extended name */ + if (fseek(arch, -sizeof(struct ar_hdr) - elen, + SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + return (arch); + } + if (fseek(arch, -elen, SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + goto skip; + } else +#endif + { +skip: + /* + * This isn't the member we're after, so we need to advance the + * stream's pointer to the start of the next header. Files are + * padded with newlines to an even-byte boundary, so we need to + * extract the size of the file from the 'size' field of the + * header and round it up during the seek. + */ + arhPtr->ar_size[sizeof(arhPtr->ar_size)-1] = '\0'; + size = (int)strtol(arhPtr->ar_size, NULL, 10); + if (fseek(arch, (size + 1) & ~1, SEEK_CUR) != 0) { + fclose(arch); + return NULL; + } + } + } + + /* + * We've looked everywhere, but the member is not to be found. Close the + * archive and return NULL -- an error. + */ + fclose(arch); + return NULL; +} + +/*- + *----------------------------------------------------------------------- + * Arch_Touch -- + * Touch a member of an archive. + * + * Input: + * gn Node of member to touch + * + * Results: + * The 'time' field of the member's header is updated. + * + * Side Effects: + * The modification time of the entire archive is also changed. + * For a library, this could necessitate the re-ranlib'ing of the + * whole thing. + * + *----------------------------------------------------------------------- + */ +void +Arch_Touch(GNode *gn) +{ + FILE * arch; /* Stream open to archive, positioned properly */ + struct ar_hdr arh; /* Current header describing member */ + char *p1, *p2; + + arch = ArchFindMember(Var_Value(ARCHIVE, gn, &p1), + Var_Value(MEMBER, gn, &p2), + &arh, "r+"); + + free(p1); + free(p2); + + snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); + + if (arch != NULL) { + (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); + fclose(arch); + } +} + +/*- + *----------------------------------------------------------------------- + * Arch_TouchLib -- + * Given a node which represents a library, touch the thing, making + * sure that the table of contents also is touched. + * + * Input: + * gn The node of the library to touch + * + * Results: + * None. + * + * Side Effects: + * Both the modification time of the library and of the RANLIBMAG + * member are set to 'now'. + * + *----------------------------------------------------------------------- + */ +void +#if !defined(RANLIBMAG) +Arch_TouchLib(GNode *gn MAKE_ATTR_UNUSED) +#else +Arch_TouchLib(GNode *gn) +#endif +{ +#ifdef RANLIBMAG + FILE * arch; /* Stream open to archive */ + struct ar_hdr arh; /* Header describing table of contents */ + struct utimbuf times; /* Times for utime() call */ + + arch = ArchFindMember(gn->path, UNCONST(RANLIBMAG), &arh, "r+"); + snprintf(arh.ar_date, sizeof(arh.ar_date), "%-12ld", (long) now); + + if (arch != NULL) { + (void)fwrite((char *)&arh, sizeof(struct ar_hdr), 1, arch); + fclose(arch); + + times.actime = times.modtime = now; + utime(gn->path, ×); + } +#endif +} + +/*- + *----------------------------------------------------------------------- + * Arch_MTime -- + * Return the modification time of a member of an archive. + * + * Input: + * gn Node describing archive member + * + * Results: + * The modification time(seconds). + * + * Side Effects: + * The mtime field of the given node is filled in with the value + * returned by the function. + * + *----------------------------------------------------------------------- + */ +time_t +Arch_MTime(GNode *gn) +{ + struct ar_hdr *arhPtr; /* Header of desired member */ + time_t modTime; /* Modification time as an integer */ + char *p1, *p2; + + arhPtr = ArchStatMember(Var_Value(ARCHIVE, gn, &p1), + Var_Value(MEMBER, gn, &p2), + TRUE); + + free(p1); + free(p2); + + if (arhPtr != NULL) { + modTime = (time_t)strtol(arhPtr->ar_date, NULL, 10); + } else { + modTime = 0; + } + + gn->mtime = modTime; + return (modTime); +} + +/*- + *----------------------------------------------------------------------- + * Arch_MemMTime -- + * Given a non-existent archive member's node, get its modification + * time from its archived form, if it exists. + * + * Results: + * The modification time. + * + * Side Effects: + * The mtime field is filled in. + * + *----------------------------------------------------------------------- + */ +time_t +Arch_MemMTime(GNode *gn) +{ + LstNode ln; + GNode *pgn; + char *nameStart, + *nameEnd; + + if (Lst_Open(gn->parents) != SUCCESS) { + gn->mtime = 0; + return (0); + } + while ((ln = Lst_Next(gn->parents)) != NULL) { + pgn = (GNode *)Lst_Datum(ln); + + if (pgn->type & OP_ARCHV) { + /* + * If the parent is an archive specification and is being made + * and its member's name matches the name of the node we were + * given, record the modification time of the parent in the + * child. We keep searching its parents in case some other + * parent requires this child to exist... + */ + nameStart = strchr(pgn->name, '(') + 1; + nameEnd = strchr(nameStart, ')'); + + if ((pgn->flags & REMAKE) && + strncmp(nameStart, gn->name, nameEnd - nameStart) == 0) { + gn->mtime = Arch_MTime(pgn); + } + } else if (pgn->flags & REMAKE) { + /* + * Something which isn't a library depends on the existence of + * this target, so it needs to exist. + */ + gn->mtime = 0; + break; + } + } + + Lst_Close(gn->parents); + + return (gn->mtime); +} + +/*- + *----------------------------------------------------------------------- + * Arch_FindLib -- + * Search for a library along the given search path. + * + * Input: + * gn Node of library to find + * path Search path + * + * Results: + * None. + * + * Side Effects: + * The node's 'path' field is set to the found path (including the + * actual file name, not -l...). If the system can handle the -L + * flag when linking (or we cannot find the library), we assume that + * the user has placed the .LIBRARIES variable in the final linking + * command (or the linker will know where to find it) and set the + * TARGET variable for this node to be the node's name. Otherwise, + * we set the TARGET variable to be the full path of the library, + * as returned by Dir_FindFile. + * + *----------------------------------------------------------------------- + */ +void +Arch_FindLib(GNode *gn, Lst path) +{ + char *libName; /* file name for archive */ + size_t sz = strlen(gn->name) + 6 - 2; + + libName = bmake_malloc(sz); + snprintf(libName, sz, "lib%s.a", &gn->name[2]); + + gn->path = Dir_FindFile(libName, path); + + free(libName); + +#ifdef LIBRARIES + Var_Set(TARGET, gn->name, gn, 0); +#else + Var_Set(TARGET, gn->path == NULL ? gn->name : gn->path, gn, 0); +#endif /* LIBRARIES */ +} + +/*- + *----------------------------------------------------------------------- + * Arch_LibOODate -- + * Decide if a node with the OP_LIB attribute is out-of-date. Called + * from Make_OODate to make its life easier. + * + * There are several ways for a library to be out-of-date that are + * not available to ordinary files. In addition, there are ways + * that are open to regular files that are not available to + * libraries. A library that is only used as a source is never + * considered out-of-date by itself. This does not preclude the + * library's modification time from making its parent be out-of-date. + * A library will be considered out-of-date for any of these reasons, + * given that it is a target on a dependency line somewhere: + * Its modification time is less than that of one of its + * sources (gn->mtime < gn->cmgn->mtime). + * Its modification time is greater than the time at which the + * make began (i.e. it's been modified in the course + * of the make, probably by archiving). + * The modification time of one of its sources is greater than + * the one of its RANLIBMAG member (i.e. its table of contents + * is out-of-date). We don't compare of the archive time + * vs. TOC time because they can be too close. In my + * opinion we should not bother with the TOC at all since + * this is used by 'ar' rules that affect the data contents + * of the archive, not by ranlib rules, which affect the + * TOC. + * + * Input: + * gn The library's graph node + * + * Results: + * TRUE if the library is out-of-date. FALSE otherwise. + * + * Side Effects: + * The library will be hashed if it hasn't been already. + * + *----------------------------------------------------------------------- + */ +Boolean +Arch_LibOODate(GNode *gn) +{ + Boolean oodate; + + if (gn->type & OP_PHONY) { + oodate = TRUE; + } else if (OP_NOP(gn->type) && Lst_IsEmpty(gn->children)) { + oodate = FALSE; + } else if ((!Lst_IsEmpty(gn->children) && gn->cmgn == NULL) || + (gn->mtime > now) || + (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime)) { + oodate = TRUE; + } else { +#ifdef RANLIBMAG + struct ar_hdr *arhPtr; /* Header for __.SYMDEF */ + int modTimeTOC; /* The table-of-contents's mod time */ + + arhPtr = ArchStatMember(gn->path, UNCONST(RANLIBMAG), FALSE); + + if (arhPtr != NULL) { + modTimeTOC = (int)strtol(arhPtr->ar_date, NULL, 10); + + if (DEBUG(ARCH) || DEBUG(MAKE)) { + fprintf(debug_file, "%s modified %s...", RANLIBMAG, Targ_FmtTime(modTimeTOC)); + } + oodate = (gn->cmgn == NULL || gn->cmgn->mtime > modTimeTOC); + } else { + /* + * A library w/o a table of contents is out-of-date + */ + if (DEBUG(ARCH) || DEBUG(MAKE)) { + fprintf(debug_file, "No t.o.c...."); + } + oodate = TRUE; + } +#else + oodate = FALSE; +#endif + } + return (oodate); +} + +/*- + *----------------------------------------------------------------------- + * Arch_Init -- + * Initialize things for this module. + * + * Results: + * None. + * + * Side Effects: + * The 'archives' list is initialized. + * + *----------------------------------------------------------------------- + */ +void +Arch_Init(void) +{ + archives = Lst_Init(FALSE); +} + + + +/*- + *----------------------------------------------------------------------- + * Arch_End -- + * Cleanup things for this module. + * + * Results: + * None. + * + * Side Effects: + * The 'archives' list is freed + * + *----------------------------------------------------------------------- + */ +void +Arch_End(void) +{ +#ifdef CLEANUP + Lst_Destroy(archives, ArchFree); +#endif +} + +/*- + *----------------------------------------------------------------------- + * Arch_IsLib -- + * Check if the node is a library + * + * Results: + * True or False. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +int +Arch_IsLib(GNode *gn) +{ + static const char armag[] = "!\n"; + char buf[sizeof(armag)-1]; + int fd; + + if ((fd = open(gn->path, O_RDONLY)) == -1) + return FALSE; + + if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { + (void)close(fd); + return FALSE; + } + + (void)close(fd); + + return memcmp(buf, armag, sizeof(buf)) == 0; +} diff --git a/usr.bin/make/buf.c b/usr.bin/make/buf.c new file mode 100644 index 0000000..ac95c16 --- /dev/null +++ b/usr.bin/make/buf.c @@ -0,0 +1,291 @@ +/* $NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)buf.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: buf.c,v 1.25 2012/04/24 20:26:58 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * buf.c -- + * Functions for automatically-expanded buffers. + */ + +#include "make.h" +#include "buf.h" + +#ifndef max +#define max(a,b) ((a) > (b) ? (a) : (b)) +#endif + +#define BUF_DEF_SIZE 256 /* Default buffer size */ + +/*- + *----------------------------------------------------------------------- + * Buf_Expand_1 -- + * Extend buffer for single byte add. + * + *----------------------------------------------------------------------- + */ +void +Buf_Expand_1(Buffer *bp) +{ + bp->size += max(bp->size, 16); + bp->buffer = bmake_realloc(bp->buffer, bp->size); +} + +/*- + *----------------------------------------------------------------------- + * Buf_AddBytes -- + * Add a number of bytes to the buffer. + * + * Results: + * None. + * + * Side Effects: + * Guess what? + * + *----------------------------------------------------------------------- + */ +void +Buf_AddBytes(Buffer *bp, int numBytes, const Byte *bytesPtr) +{ + int count = bp->count; + Byte *ptr; + + if (__predict_false(count + numBytes >= bp->size)) { + bp->size += max(bp->size, numBytes + 16); + bp->buffer = bmake_realloc(bp->buffer, bp->size); + } + + ptr = bp->buffer + count; + bp->count = count + numBytes; + ptr[numBytes] = 0; + memcpy(ptr, bytesPtr, numBytes); +} + +/*- + *----------------------------------------------------------------------- + * Buf_GetAll -- + * Get all the available data at once. + * + * Results: + * A pointer to the data and the number of bytes available. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Byte * +Buf_GetAll(Buffer *bp, int *numBytesPtr) +{ + + if (numBytesPtr != NULL) + *numBytesPtr = bp->count; + + return (bp->buffer); +} + +/*- + *----------------------------------------------------------------------- + * Buf_Empty -- + * Throw away bytes in a buffer. + * + * Results: + * None. + * + * Side Effects: + * The bytes are discarded. + * + *----------------------------------------------------------------------- + */ +void +Buf_Empty(Buffer *bp) +{ + + bp->count = 0; + *bp->buffer = 0; +} + +/*- + *----------------------------------------------------------------------- + * Buf_Init -- + * Initialize a buffer. If no initial size is given, a reasonable + * default is used. + * + * Input: + * size Initial size for the buffer + * + * Results: + * A buffer to be given to other functions in this library. + * + * Side Effects: + * The buffer is created, the space allocated and pointers + * initialized. + * + *----------------------------------------------------------------------- + */ +void +Buf_Init(Buffer *bp, int size) +{ + if (size <= 0) { + size = BUF_DEF_SIZE; + } + bp->size = size; + bp->count = 0; + bp->buffer = bmake_malloc(size); + *bp->buffer = 0; +} + +/*- + *----------------------------------------------------------------------- + * Buf_Destroy -- + * Nuke a buffer and all its resources. + * + * Input: + * buf Buffer to destroy + * freeData TRUE if the data should be destroyed + * + * Results: + * Data buffer, NULL if freed + * + * Side Effects: + * The buffer is freed. + * + *----------------------------------------------------------------------- + */ +Byte * +Buf_Destroy(Buffer *buf, Boolean freeData) +{ + Byte *data; + + data = buf->buffer; + if (freeData) { + free(data); + data = NULL; + } + + buf->size = 0; + buf->count = 0; + buf->buffer = NULL; + + return data; +} + + +/*- + *----------------------------------------------------------------------- + * Buf_DestroyCompact -- + * Nuke a buffer and return its data. + * + * Input: + * buf Buffer to destroy + * + * Results: + * Data buffer + * + * Side Effects: + * If the buffer size is much greater than its content, + * a new buffer will be allocated and the old one freed. + * + *----------------------------------------------------------------------- + */ +#ifndef BUF_COMPACT_LIMIT +# define BUF_COMPACT_LIMIT 128 /* worthwhile saving */ +#endif + +Byte * +Buf_DestroyCompact(Buffer *buf) +{ +#if BUF_COMPACT_LIMIT > 0 + Byte *data; + + if (buf->size - buf->count >= BUF_COMPACT_LIMIT) { + /* We trust realloc to be smart */ + data = bmake_realloc(buf->buffer, buf->count + 1); + if (data) { + data[buf->count] = 0; + Buf_Destroy(buf, FALSE); + return data; + } + } +#endif + return Buf_Destroy(buf, FALSE); +} diff --git a/usr.bin/make/buf.h b/usr.bin/make/buf.h new file mode 100644 index 0000000..7bd2d2b --- /dev/null +++ b/usr.bin/make/buf.h @@ -0,0 +1,119 @@ +/* $NetBSD: buf.h,v 1.19 2017/05/31 22:02:06 maya Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)buf.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)buf.h 8.1 (Berkeley) 6/6/93 + */ + +/*- + * buf.h -- + * Header for users of the buf library. + */ + +#ifndef MAKE_BUF_H +#define MAKE_BUF_H + +typedef char Byte; + +typedef struct Buffer { + int size; /* Current size of the buffer */ + int count; /* Number of bytes in buffer */ + Byte *buffer; /* The buffer itself (zero terminated) */ +} Buffer; + +/* If we aren't on netbsd, __predict_false() might not be defined. */ +#ifndef __predict_false +#define __predict_false(x) (x) +#endif + +/* Buf_AddByte adds a single byte to a buffer. */ +#define Buf_AddByte(bp, byte) do { \ + int _count = ++(bp)->count; \ + char *_ptr; \ + if (__predict_false(_count >= (bp)->size)) \ + Buf_Expand_1(bp); \ + _ptr = (bp)->buffer + _count; \ + _ptr[-1] = (byte); \ + _ptr[0] = 0; \ + } while (0) + +#define BUF_ERROR 256 + +#define Buf_Size(bp) ((bp)->count) + +void Buf_Expand_1(Buffer *); +void Buf_AddBytes(Buffer *, int, const Byte *); +Byte *Buf_GetAll(Buffer *, int *); +void Buf_Empty(Buffer *); +void Buf_Init(Buffer *, int); +Byte *Buf_Destroy(Buffer *, Boolean); +Byte *Buf_DestroyCompact(Buffer *); + +#endif /* MAKE_BUF_H */ diff --git a/usr.bin/make/compat.c b/usr.bin/make/compat.c new file mode 100644 index 0000000..538c456 --- /dev/null +++ b/usr.bin/make/compat.c @@ -0,0 +1,778 @@ +/* $NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)compat.c 8.2 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: compat.c,v 1.107 2017/07/20 19:29:54 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * compat.c -- + * The routines in this file implement the full-compatibility + * mode of PMake. Most of the special functionality of PMake + * is available in this mode. Things not supported: + * - different shells. + * - friendly variable substitution. + * + * Interface: + * Compat_Run Initialize things for this module and recreate + * thems as need creatin' + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" +#include "metachar.h" +#include "pathnames.h" + + +static GNode *curTarg = NULL; +static GNode *ENDNode; +static void CompatInterrupt(int); +static pid_t compatChild; +static int compatSigno; + +/* + * CompatDeleteTarget -- delete a failed, interrupted, or otherwise + * duffed target if not inhibited by .PRECIOUS. + */ +static void +CompatDeleteTarget(GNode *gn) +{ + if ((gn != NULL) && !Targ_Precious (gn)) { + char *p1; + char *file = Var_Value(TARGET, gn, &p1); + + if (!noExecute && eunlink(file) != -1) { + Error("*** %s removed", file); + } + + free(p1); + } +} + +/*- + *----------------------------------------------------------------------- + * CompatInterrupt -- + * Interrupt the creation of the current target and remove it if + * it ain't precious. + * + * Results: + * None. + * + * Side Effects: + * The target is removed and the process exits. If .INTERRUPT exists, + * its commands are run first WITH INTERRUPTS IGNORED.. + * + * XXX: is .PRECIOUS supposed to inhibit .INTERRUPT? I doubt it, but I've + * left the logic alone for now. - dholland 20160826 + * + *----------------------------------------------------------------------- + */ +static void +CompatInterrupt(int signo) +{ + GNode *gn; + + CompatDeleteTarget(curTarg); + + if ((curTarg != NULL) && !Targ_Precious (curTarg)) { + /* + * Run .INTERRUPT only if hit with interrupt signal + */ + if (signo == SIGINT) { + gn = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); + if (gn != NULL) { + Compat_Make(gn, gn); + } + } + } + if (signo == SIGQUIT) + _exit(signo); + /* + * If there is a child running, pass the signal on + * we will exist after it has exited. + */ + compatSigno = signo; + if (compatChild > 0) { + KILLPG(compatChild, signo); + } else { + bmake_signal(signo, SIG_DFL); + kill(myPid, signo); + } +} + +/*- + *----------------------------------------------------------------------- + * CompatRunCommand -- + * Execute the next command for a target. If the command returns an + * error, the node's made field is set to ERROR and creation stops. + * + * Input: + * cmdp Command to execute + * gnp Node from which the command came + * + * Results: + * 0 if the command succeeded, 1 if an error occurred. + * + * Side Effects: + * The node's 'made' field may be set to ERROR. + * + *----------------------------------------------------------------------- + */ +int +CompatRunCommand(void *cmdp, void *gnp) +{ + char *cmdStart; /* Start of expanded command */ + char *cp, *bp; + Boolean silent, /* Don't print command */ + doIt; /* Execute even if -n */ + volatile Boolean errCheck; /* Check errors */ + int reason; /* Reason for child's death */ + int status; /* Description of child's death */ + pid_t cpid; /* Child actually found */ + pid_t retstat; /* Result of wait */ + LstNode cmdNode; /* Node where current command is located */ + const char ** volatile av; /* Argument vector for thing to exec */ + char ** volatile mav;/* Copy of the argument vector for freeing */ + int argc; /* Number of arguments in av or 0 if not + * dynamically allocated */ + Boolean local; /* TRUE if command should be executed + * locally */ + Boolean useShell; /* TRUE if command should be executed + * using a shell */ + char * volatile cmd = (char *)cmdp; + GNode *gn = (GNode *)gnp; + + silent = gn->type & OP_SILENT; + errCheck = !(gn->type & OP_IGNORE); + doIt = FALSE; + + cmdNode = Lst_Member(gn->commands, cmd); + cmdStart = Var_Subst(NULL, cmd, gn, VARF_WANTRES); + + /* + * brk_string will return an argv with a NULL in av[0], thus causing + * execvp to choke and die horribly. Besides, how can we execute a null + * command? In any case, we warn the user that the command expanded to + * nothing (is this the right thing to do?). + */ + + if (*cmdStart == '\0') { + free(cmdStart); + return(0); + } + cmd = cmdStart; + Lst_Replace(cmdNode, cmdStart); + + if ((gn->type & OP_SAVE_CMDS) && (gn != ENDNode)) { + (void)Lst_AtEnd(ENDNode->commands, cmdStart); + return(0); + } + if (strcmp(cmdStart, "...") == 0) { + gn->type |= OP_SAVE_CMDS; + return(0); + } + + while ((*cmd == '@') || (*cmd == '-') || (*cmd == '+')) { + switch (*cmd) { + case '@': + silent = DEBUG(LOUD) ? FALSE : TRUE; + break; + case '-': + errCheck = FALSE; + break; + case '+': + doIt = TRUE; + if (!shellName) /* we came here from jobs */ + Shell_Init(); + break; + } + cmd++; + } + + while (isspace((unsigned char)*cmd)) + cmd++; + + /* + * If we did not end up with a command, just skip it. + */ + if (!*cmd) + return (0); + +#if !defined(MAKE_NATIVE) + /* + * In a non-native build, the host environment might be weird enough + * that it's necessary to go through a shell to get the correct + * behaviour. Or perhaps the shell has been replaced with something + * that does extra logging, and that should not be bypassed. + */ + useShell = TRUE; +#else + /* + * Search for meta characters in the command. If there are no meta + * characters, there's no need to execute a shell to execute the + * command. + * + * Additionally variable assignments and empty commands + * go to the shell. Therefore treat '=' and ':' like shell + * meta characters as documented in make(1). + */ + + useShell = needshell(cmd, FALSE); +#endif + + /* + * Print the command before echoing if we're not supposed to be quiet for + * this one. We also print the command if -n given. + */ + if (!silent || NoExecute(gn)) { + printf("%s\n", cmd); + fflush(stdout); + } + + /* + * If we're not supposed to execute any commands, this is as far as + * we go... + */ + if (!doIt && NoExecute(gn)) { + return (0); + } + if (DEBUG(JOB)) + fprintf(debug_file, "Execute: '%s'\n", cmd); + +again: + if (useShell) { + /* + * We need to pass the command off to the shell, typically + * because the command contains a "meta" character. + */ + static const char *shargv[5]; + int shargc; + + shargc = 0; + shargv[shargc++] = shellPath; + /* + * The following work for any of the builtin shell specs. + */ + if (errCheck && shellErrFlag) { + shargv[shargc++] = shellErrFlag; + } + if (DEBUG(SHELL)) + shargv[shargc++] = "-xc"; + else + shargv[shargc++] = "-c"; + shargv[shargc++] = cmd; + shargv[shargc++] = NULL; + av = shargv; + argc = 0; + bp = NULL; + mav = NULL; + } else { + /* + * No meta-characters, so no need to exec a shell. Break the command + * into words to form an argument vector we can execute. + */ + mav = brk_string(cmd, &argc, TRUE, &bp); + if (mav == NULL) { + useShell = 1; + goto again; + } + av = (void *)mav; + } + + local = TRUE; + +#ifdef USE_META + if (useMeta) { + meta_compat_start(); + } +#endif + + /* + * Fork and execute the single command. If the fork fails, we abort. + */ + compatChild = cpid = vFork(); + if (cpid < 0) { + Fatal("Could not fork"); + } + if (cpid == 0) { + Var_ExportVars(); +#ifdef USE_META + if (useMeta) { + meta_compat_child(); + } +#endif + if (local) + (void)execvp(av[0], (char *const *)UNCONST(av)); + else + (void)execv(av[0], (char *const *)UNCONST(av)); + execError("exec", av[0]); + _exit(1); + } + + free(mav); + free(bp); + + Lst_Replace(cmdNode, NULL); + +#ifdef USE_META + if (useMeta) { + meta_compat_parent(); + } +#endif + + /* + * The child is off and running. Now all we can do is wait... + */ + while (1) { + + while ((retstat = wait(&reason)) != cpid) { + if (retstat > 0) + JobReapChild(retstat, reason, FALSE); /* not ours? */ + if (retstat == -1 && errno != EINTR) { + break; + } + } + + if (retstat > -1) { + if (WIFSTOPPED(reason)) { + status = WSTOPSIG(reason); /* stopped */ + } else if (WIFEXITED(reason)) { + status = WEXITSTATUS(reason); /* exited */ +#if defined(USE_META) && defined(USE_FILEMON_ONCE) + if (useMeta) { + meta_cmd_finish(NULL); + } +#endif + if (status != 0) { + if (DEBUG(ERROR)) { + fprintf(debug_file, "\n*** Failed target: %s\n*** Failed command: ", + gn->name); + for (cp = cmd; *cp; ) { + if (isspace((unsigned char)*cp)) { + fprintf(debug_file, " "); + while (isspace((unsigned char)*cp)) + cp++; + } else { + fprintf(debug_file, "%c", *cp); + cp++; + } + } + fprintf(debug_file, "\n"); + } + printf("*** Error code %d", status); + } + } else { + status = WTERMSIG(reason); /* signaled */ + printf("*** Signal %d", status); + } + + + if (!WIFEXITED(reason) || (status != 0)) { + if (errCheck) { +#ifdef USE_META + if (useMeta) { + meta_job_error(NULL, gn, 0, status); + } +#endif + gn->made = ERROR; + if (keepgoing) { + /* + * Abort the current target, but let others + * continue. + */ + printf(" (continuing)\n"); + } else { + printf("\n"); + } + if (deleteOnError) { + CompatDeleteTarget(gn); + } + } else { + /* + * Continue executing commands for this target. + * If we return 0, this will happen... + */ + printf(" (ignored)\n"); + status = 0; + } + } + break; + } else { + Fatal("error in wait: %d: %s", retstat, strerror(errno)); + /*NOTREACHED*/ + } + } + free(cmdStart); + compatChild = 0; + if (compatSigno) { + bmake_signal(compatSigno, SIG_DFL); + kill(myPid, compatSigno); + } + + return (status); +} + +/*- + *----------------------------------------------------------------------- + * Compat_Make -- + * Make a target. + * + * Input: + * gnp The node to make + * pgnp Parent to abort if necessary + * + * Results: + * 0 + * + * Side Effects: + * If an error is detected and not being ignored, the process exits. + * + *----------------------------------------------------------------------- + */ +int +Compat_Make(void *gnp, void *pgnp) +{ + GNode *gn = (GNode *)gnp; + GNode *pgn = (GNode *)pgnp; + + if (!shellName) /* we came here from jobs */ + Shell_Init(); + if (gn->made == UNMADE && (gn == pgn || (pgn->type & OP_MADE) == 0)) { + /* + * First mark ourselves to be made, then apply whatever transformations + * the suffix module thinks are necessary. Once that's done, we can + * descend and make all our children. If any of them has an error + * but the -k flag was given, our 'make' field will be set FALSE again. + * This is our signal to not attempt to do anything but abort our + * parent as well. + */ + gn->flags |= REMAKE; + gn->made = BEINGMADE; + if ((gn->type & OP_MADE) == 0) + Suff_FindDeps(gn); + Lst_ForEach(gn->children, Compat_Make, gn); + if ((gn->flags & REMAKE) == 0) { + gn->made = ABORTED; + pgn->flags &= ~REMAKE; + goto cohorts; + } + + if (Lst_Member(gn->iParents, pgn) != NULL) { + char *p1; + Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); + free(p1); + } + + /* + * All the children were made ok. Now cmgn->mtime contains the + * modification time of the newest child, we need to find out if we + * exist and when we were modified last. The criteria for datedness + * are defined by the Make_OODate function. + */ + if (DEBUG(MAKE)) { + fprintf(debug_file, "Examining %s...", gn->name); + } + if (! Make_OODate(gn)) { + gn->made = UPTODATE; + if (DEBUG(MAKE)) { + fprintf(debug_file, "up-to-date.\n"); + } + goto cohorts; + } else if (DEBUG(MAKE)) { + fprintf(debug_file, "out-of-date.\n"); + } + + /* + * If the user is just seeing if something is out-of-date, exit now + * to tell him/her "yes". + */ + if (queryFlag) { + exit(1); + } + + /* + * We need to be re-made. We also have to make sure we've got a $? + * variable. To be nice, we also define the $> variable using + * Make_DoAllVar(). + */ + Make_DoAllVar(gn); + + /* + * Alter our type to tell if errors should be ignored or things + * should not be printed so CompatRunCommand knows what to do. + */ + if (Targ_Ignore(gn)) { + gn->type |= OP_IGNORE; + } + if (Targ_Silent(gn)) { + gn->type |= OP_SILENT; + } + + if (Job_CheckCommands(gn, Fatal)) { + /* + * Our commands are ok, but we still have to worry about the -t + * flag... + */ + if (!touchFlag || (gn->type & OP_MAKE)) { + curTarg = gn; +#ifdef USE_META + if (useMeta && !NoExecute(gn)) { + meta_job_start(NULL, gn); + } +#endif + Lst_ForEach(gn->commands, CompatRunCommand, gn); + curTarg = NULL; + } else { + Job_Touch(gn, gn->type & OP_SILENT); + } + } else { + gn->made = ERROR; + } +#ifdef USE_META + if (useMeta && !NoExecute(gn)) { + if (meta_job_finish(NULL) != 0) + gn->made = ERROR; + } +#endif + + if (gn->made != ERROR) { + /* + * If the node was made successfully, mark it so, update + * its modification time and timestamp all its parents. Note + * that for .ZEROTIME targets, the timestamping isn't done. + * This is to keep its state from affecting that of its parent. + */ + gn->made = MADE; + pgn->flags |= Make_Recheck(gn) == 0 ? FORCE : 0; + if (!(gn->type & OP_EXEC)) { + pgn->flags |= CHILDMADE; + Make_TimeStamp(pgn, gn); + } + } else if (keepgoing) { + pgn->flags &= ~REMAKE; + } else { + PrintOnError(gn, "\nStop."); + exit(1); + } + } else if (gn->made == ERROR) { + /* + * Already had an error when making this beastie. Tell the parent + * to abort. + */ + pgn->flags &= ~REMAKE; + } else { + if (Lst_Member(gn->iParents, pgn) != NULL) { + char *p1; + Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), pgn, 0); + free(p1); + } + switch(gn->made) { + case BEINGMADE: + Error("Graph cycles through %s", gn->name); + gn->made = ERROR; + pgn->flags &= ~REMAKE; + break; + case MADE: + if ((gn->type & OP_EXEC) == 0) { + pgn->flags |= CHILDMADE; + Make_TimeStamp(pgn, gn); + } + break; + case UPTODATE: + if ((gn->type & OP_EXEC) == 0) { + Make_TimeStamp(pgn, gn); + } + break; + default: + break; + } + } + +cohorts: + Lst_ForEach(gn->cohorts, Compat_Make, pgnp); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Compat_Run -- + * Initialize this mode and start making. + * + * Input: + * targs List of target nodes to re-create + * + * Results: + * None. + * + * Side Effects: + * Guess what? + * + *----------------------------------------------------------------------- + */ +void +Compat_Run(Lst targs) +{ + GNode *gn = NULL;/* Current root target */ + int errors; /* Number of targets not remade due to errors */ + + if (!shellName) + Shell_Init(); + + if (bmake_signal(SIGINT, SIG_IGN) != SIG_IGN) { + bmake_signal(SIGINT, CompatInterrupt); + } + if (bmake_signal(SIGTERM, SIG_IGN) != SIG_IGN) { + bmake_signal(SIGTERM, CompatInterrupt); + } + if (bmake_signal(SIGHUP, SIG_IGN) != SIG_IGN) { + bmake_signal(SIGHUP, CompatInterrupt); + } + if (bmake_signal(SIGQUIT, SIG_IGN) != SIG_IGN) { + bmake_signal(SIGQUIT, CompatInterrupt); + } + + ENDNode = Targ_FindNode(".END", TARG_CREATE); + ENDNode->type = OP_SPECIAL; + /* + * If the user has defined a .BEGIN target, execute the commands attached + * to it. + */ + if (!queryFlag) { + gn = Targ_FindNode(".BEGIN", TARG_NOCREATE); + if (gn != NULL) { + Compat_Make(gn, gn); + if (gn->made == ERROR) { + PrintOnError(gn, "\nStop."); + exit(1); + } + } + } + + /* + * Expand .USE nodes right now, because they can modify the structure + * of the tree. + */ + Make_ExpandUse(targs); + + /* + * For each entry in the list of targets to create, call Compat_Make on + * it to create the thing. Compat_Make will leave the 'made' field of gn + * in one of several states: + * UPTODATE gn was already up-to-date + * MADE gn was recreated successfully + * ERROR An error occurred while gn was being created + * ABORTED gn was not remade because one of its inferiors + * could not be made due to errors. + */ + errors = 0; + while (!Lst_IsEmpty (targs)) { + gn = (GNode *)Lst_DeQueue(targs); + Compat_Make(gn, gn); + + if (gn->made == UPTODATE) { + printf("`%s' is up to date.\n", gn->name); + } else if (gn->made == ABORTED) { + printf("`%s' not remade because of errors.\n", gn->name); + errors += 1; + } + } + + /* + * If the user has defined a .END target, run its commands. + */ + if (errors == 0) { + Compat_Make(ENDNode, ENDNode); + if (gn->made == ERROR) { + PrintOnError(gn, "\nStop."); + exit(1); + } + } +} diff --git a/usr.bin/make/cond.c b/usr.bin/make/cond.c new file mode 100644 index 0000000..7c9c96a --- /dev/null +++ b/usr.bin/make/cond.c @@ -0,0 +1,1436 @@ +/* $NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)cond.c 8.2 (Berkeley) 1/2/94"; +#else +__RCSID("$NetBSD: cond.c,v 1.75 2017/04/16 20:59:04 riastradh Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * cond.c -- + * Functions to handle conditionals in a makefile. + * + * Interface: + * Cond_Eval Evaluate the conditional in the passed line. + * + */ + +#include +#include +#include /* For strtoul() error checking */ + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "buf.h" + +/* + * The parsing of conditional expressions is based on this grammar: + * E -> F || E + * E -> F + * F -> T && F + * F -> T + * T -> defined(variable) + * T -> make(target) + * T -> exists(file) + * T -> empty(varspec) + * T -> target(name) + * T -> commands(name) + * T -> symbol + * T -> $(varspec) op value + * T -> $(varspec) == "string" + * T -> $(varspec) != "string" + * T -> "string" + * T -> ( E ) + * T -> ! T + * op -> == | != | > | < | >= | <= + * + * 'symbol' is some other symbol to which the default function (condDefProc) + * is applied. + * + * Tokens are scanned from the 'condExpr' string. The scanner (CondToken) + * will return TOK_AND for '&' and '&&', TOK_OR for '|' and '||', + * TOK_NOT for '!', TOK_LPAREN for '(', TOK_RPAREN for ')' and will evaluate + * the other terminal symbols, using either the default function or the + * function given in the terminal, and return the result as either TOK_TRUE + * or TOK_FALSE. + * + * TOK_FALSE is 0 and TOK_TRUE 1 so we can directly assign C comparisons. + * + * All Non-Terminal functions (CondE, CondF and CondT) return TOK_ERROR on + * error. + */ +typedef enum { + TOK_FALSE = 0, TOK_TRUE = 1, TOK_AND, TOK_OR, TOK_NOT, + TOK_LPAREN, TOK_RPAREN, TOK_EOF, TOK_NONE, TOK_ERROR +} Token; + +/*- + * Structures to handle elegantly the different forms of #if's. The + * last two fields are stored in condInvert and condDefProc, respectively. + */ +static void CondPushBack(Token); +static int CondGetArg(char **, char **, const char *); +static Boolean CondDoDefined(int, const char *); +static int CondStrMatch(const void *, const void *); +static Boolean CondDoMake(int, const char *); +static Boolean CondDoExists(int, const char *); +static Boolean CondDoTarget(int, const char *); +static Boolean CondDoCommands(int, const char *); +static Boolean CondCvtArg(char *, double *); +static Token CondToken(Boolean); +static Token CondT(Boolean); +static Token CondF(Boolean); +static Token CondE(Boolean); +static int do_Cond_EvalExpression(Boolean *); + +static const struct If { + const char *form; /* Form of if */ + int formlen; /* Length of form */ + Boolean doNot; /* TRUE if default function should be negated */ + Boolean (*defProc)(int, const char *); /* Default function to apply */ +} ifs[] = { + { "def", 3, FALSE, CondDoDefined }, + { "ndef", 4, TRUE, CondDoDefined }, + { "make", 4, FALSE, CondDoMake }, + { "nmake", 5, TRUE, CondDoMake }, + { "", 0, FALSE, CondDoDefined }, + { NULL, 0, FALSE, NULL } +}; + +static const struct If *if_info; /* Info for current statement */ +static char *condExpr; /* The expression to parse */ +static Token condPushBack=TOK_NONE; /* Single push-back token used in + * parsing */ + +static unsigned int cond_depth = 0; /* current .if nesting level */ +static unsigned int cond_min_depth = 0; /* depth at makefile open */ + +/* + * Indicate when we should be strict about lhs of comparisons. + * TRUE when Cond_EvalExpression is called from Cond_Eval (.if etc) + * FALSE when Cond_EvalExpression is called from var.c:ApplyModifiers + * since lhs is already expanded and we cannot tell if + * it was a variable reference or not. + */ +static Boolean lhsStrict; + +static int +istoken(const char *str, const char *tok, size_t len) +{ + return strncmp(str, tok, len) == 0 && !isalpha((unsigned char)str[len]); +} + +/*- + *----------------------------------------------------------------------- + * CondPushBack -- + * Push back the most recent token read. We only need one level of + * this, so the thing is just stored in 'condPushback'. + * + * Input: + * t Token to push back into the "stream" + * + * Results: + * None. + * + * Side Effects: + * condPushback is overwritten. + * + *----------------------------------------------------------------------- + */ +static void +CondPushBack(Token t) +{ + condPushBack = t; +} + +/*- + *----------------------------------------------------------------------- + * CondGetArg -- + * Find the argument of a built-in function. + * + * Input: + * parens TRUE if arg should be bounded by parens + * + * Results: + * The length of the argument and the address of the argument. + * + * Side Effects: + * The pointer is set to point to the closing parenthesis of the + * function call. + * + *----------------------------------------------------------------------- + */ +static int +CondGetArg(char **linePtr, char **argPtr, const char *func) +{ + char *cp; + int argLen; + Buffer buf; + int paren_depth; + char ch; + + cp = *linePtr; + if (func != NULL) + /* Skip opening '(' - verfied by caller */ + cp++; + + if (*cp == '\0') { + /* + * No arguments whatsoever. Because 'make' and 'defined' aren't really + * "reserved words", we don't print a message. I think this is better + * than hitting the user with a warning message every time s/he uses + * the word 'make' or 'defined' at the beginning of a symbol... + */ + *argPtr = NULL; + return (0); + } + + while (*cp == ' ' || *cp == '\t') { + cp++; + } + + /* + * Create a buffer for the argument and start it out at 16 characters + * long. Why 16? Why not? + */ + Buf_Init(&buf, 16); + + paren_depth = 0; + for (;;) { + ch = *cp; + if (ch == 0 || ch == ' ' || ch == '\t') + break; + if ((ch == '&' || ch == '|') && paren_depth == 0) + break; + if (*cp == '$') { + /* + * Parse the variable spec and install it as part of the argument + * if it's valid. We tell Var_Parse to complain on an undefined + * variable, so we don't do it too. Nor do we return an error, + * though perhaps we should... + */ + char *cp2; + int len; + void *freeIt; + + cp2 = Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES, + &len, &freeIt); + Buf_AddBytes(&buf, strlen(cp2), cp2); + free(freeIt); + cp += len; + continue; + } + if (ch == '(') + paren_depth++; + else + if (ch == ')' && --paren_depth < 0) + break; + Buf_AddByte(&buf, *cp); + cp++; + } + + *argPtr = Buf_GetAll(&buf, &argLen); + Buf_Destroy(&buf, FALSE); + + while (*cp == ' ' || *cp == '\t') { + cp++; + } + + if (func != NULL && *cp++ != ')') { + Parse_Error(PARSE_WARNING, "Missing closing parenthesis for %s()", + func); + return (0); + } + + *linePtr = cp; + return (argLen); +} + +/*- + *----------------------------------------------------------------------- + * CondDoDefined -- + * Handle the 'defined' function for conditionals. + * + * Results: + * TRUE if the given variable is defined. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoDefined(int argLen MAKE_ATTR_UNUSED, const char *arg) +{ + char *p1; + Boolean result; + + if (Var_Value(arg, VAR_CMD, &p1) != NULL) { + result = TRUE; + } else { + result = FALSE; + } + + free(p1); + return (result); +} + +/*- + *----------------------------------------------------------------------- + * CondStrMatch -- + * Front-end for Str_Match so it returns 0 on match and non-zero + * on mismatch. Callback function for CondDoMake via Lst_Find + * + * Results: + * 0 if string matches pattern + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +static int +CondStrMatch(const void *string, const void *pattern) +{ + return(!Str_Match(string, pattern)); +} + +/*- + *----------------------------------------------------------------------- + * CondDoMake -- + * Handle the 'make' function for conditionals. + * + * Results: + * TRUE if the given target is being made. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoMake(int argLen MAKE_ATTR_UNUSED, const char *arg) +{ + return Lst_Find(create, arg, CondStrMatch) != NULL; +} + +/*- + *----------------------------------------------------------------------- + * CondDoExists -- + * See if the given file exists. + * + * Results: + * TRUE if the file exists and FALSE if it does not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoExists(int argLen MAKE_ATTR_UNUSED, const char *arg) +{ + Boolean result; + char *path; + + path = Dir_FindFile(arg, dirSearchPath); + if (DEBUG(COND)) { + fprintf(debug_file, "exists(%s) result is \"%s\"\n", + arg, path ? path : ""); + } + if (path != NULL) { + result = TRUE; + free(path); + } else { + result = FALSE; + } + return (result); +} + +/*- + *----------------------------------------------------------------------- + * CondDoTarget -- + * See if the given node exists and is an actual target. + * + * Results: + * TRUE if the node exists as a target and FALSE if it does not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoTarget(int argLen MAKE_ATTR_UNUSED, const char *arg) +{ + GNode *gn; + + gn = Targ_FindNode(arg, TARG_NOCREATE); + return (gn != NULL) && !OP_NOP(gn->type); +} + +/*- + *----------------------------------------------------------------------- + * CondDoCommands -- + * See if the given node exists and is an actual target with commands + * associated with it. + * + * Results: + * TRUE if the node exists as a target and has commands associated with + * it and FALSE if it does not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +CondDoCommands(int argLen MAKE_ATTR_UNUSED, const char *arg) +{ + GNode *gn; + + gn = Targ_FindNode(arg, TARG_NOCREATE); + return (gn != NULL) && !OP_NOP(gn->type) && !Lst_IsEmpty(gn->commands); +} + +/*- + *----------------------------------------------------------------------- + * CondCvtArg -- + * Convert the given number into a double. + * We try a base 10 or 16 integer conversion first, if that fails + * then we try a floating point conversion instead. + * + * Results: + * Sets 'value' to double value of string. + * Returns 'true' if the convertion suceeded + * + *----------------------------------------------------------------------- + */ +static Boolean +CondCvtArg(char *str, double *value) +{ + char *eptr, ech; + unsigned long l_val; + double d_val; + + errno = 0; + if (!*str) { + *value = (double)0; + return TRUE; + } + l_val = strtoul(str, &eptr, str[1] == 'x' ? 16 : 10); + ech = *eptr; + if (ech == 0 && errno != ERANGE) { + d_val = str[0] == '-' ? -(double)-l_val : (double)l_val; + } else { + if (ech != 0 && ech != '.' && ech != 'e' && ech != 'E') + return FALSE; + d_val = strtod(str, &eptr); + if (*eptr) + return FALSE; + } + + *value = d_val; + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * CondGetString -- + * Get a string from a variable reference or an optionally quoted + * string. This is called for the lhs and rhs of string compares. + * + * Results: + * Sets freeIt if needed, + * Sets quoted if string was quoted, + * Returns NULL on error, + * else returns string - absent any quotes. + * + * Side Effects: + * Moves condExpr to end of this token. + * + * + *----------------------------------------------------------------------- + */ +/* coverity:[+alloc : arg-*2] */ +static char * +CondGetString(Boolean doEval, Boolean *quoted, void **freeIt, Boolean strictLHS) +{ + Buffer buf; + char *cp; + char *str; + int len; + int qt; + char *start; + + Buf_Init(&buf, 0); + str = NULL; + *freeIt = NULL; + *quoted = qt = *condExpr == '"' ? 1 : 0; + if (qt) + condExpr++; + for (start = condExpr; *condExpr && str == NULL; condExpr++) { + switch (*condExpr) { + case '\\': + if (condExpr[1] != '\0') { + condExpr++; + Buf_AddByte(&buf, *condExpr); + } + break; + case '"': + if (qt) { + condExpr++; /* we don't want the quotes */ + goto got_str; + } else + Buf_AddByte(&buf, *condExpr); /* likely? */ + break; + case ')': + case '!': + case '=': + case '>': + case '<': + case ' ': + case '\t': + if (!qt) + goto got_str; + else + Buf_AddByte(&buf, *condExpr); + break; + case '$': + /* if we are in quotes, then an undefined variable is ok */ + str = Var_Parse(condExpr, VAR_CMD, + ((!qt && doEval) ? VARF_UNDEFERR : 0) | + VARF_WANTRES, &len, freeIt); + if (str == var_Error) { + if (*freeIt) { + free(*freeIt); + *freeIt = NULL; + } + /* + * Even if !doEval, we still report syntax errors, which + * is what getting var_Error back with !doEval means. + */ + str = NULL; + goto cleanup; + } + condExpr += len; + /* + * If the '$' was first char (no quotes), and we are + * followed by space, the operator or end of expression, + * we are done. + */ + if ((condExpr == start + len) && + (*condExpr == '\0' || + isspace((unsigned char) *condExpr) || + strchr("!=><)", *condExpr))) { + goto cleanup; + } + /* + * Nope, we better copy str to buf + */ + for (cp = str; *cp; cp++) { + Buf_AddByte(&buf, *cp); + } + if (*freeIt) { + free(*freeIt); + *freeIt = NULL; + } + str = NULL; /* not finished yet */ + condExpr--; /* don't skip over next char */ + break; + default: + if (strictLHS && !qt && *start != '$' && + !isdigit((unsigned char) *start)) { + /* lhs must be quoted, a variable reference or number */ + if (*freeIt) { + free(*freeIt); + *freeIt = NULL; + } + str = NULL; + goto cleanup; + } + Buf_AddByte(&buf, *condExpr); + break; + } + } + got_str: + str = Buf_GetAll(&buf, NULL); + *freeIt = str; + cleanup: + Buf_Destroy(&buf, FALSE); + return str; +} + +/*- + *----------------------------------------------------------------------- + * CondToken -- + * Return the next token from the input. + * + * Results: + * A Token for the next lexical token in the stream. + * + * Side Effects: + * condPushback will be set back to TOK_NONE if it is used. + * + *----------------------------------------------------------------------- + */ +static Token +compare_expression(Boolean doEval) +{ + Token t; + char *lhs; + char *rhs; + char *op; + void *lhsFree; + void *rhsFree; + Boolean lhsQuoted; + Boolean rhsQuoted; + double left, right; + + t = TOK_ERROR; + rhs = NULL; + lhsFree = rhsFree = FALSE; + lhsQuoted = rhsQuoted = FALSE; + + /* + * Parse the variable spec and skip over it, saving its + * value in lhs. + */ + lhs = CondGetString(doEval, &lhsQuoted, &lhsFree, lhsStrict); + if (!lhs) + goto done; + + /* + * Skip whitespace to get to the operator + */ + while (isspace((unsigned char) *condExpr)) + condExpr++; + + /* + * Make sure the operator is a valid one. If it isn't a + * known relational operator, pretend we got a + * != 0 comparison. + */ + op = condExpr; + switch (*condExpr) { + case '!': + case '=': + case '<': + case '>': + if (condExpr[1] == '=') { + condExpr += 2; + } else { + condExpr += 1; + } + break; + default: + if (!doEval) { + t = TOK_FALSE; + goto done; + } + /* For .ifxxx "..." check for non-empty string. */ + if (lhsQuoted) { + t = lhs[0] != 0; + goto done; + } + /* For .ifxxx compare against zero */ + if (CondCvtArg(lhs, &left)) { + t = left != 0.0; + goto done; + } + /* For .if ${...} check for non-empty string (defProc is ifdef). */ + if (if_info->form[0] == 0) { + t = lhs[0] != 0; + goto done; + } + /* Otherwise action default test ... */ + t = if_info->defProc(strlen(lhs), lhs) != if_info->doNot; + goto done; + } + + while (isspace((unsigned char)*condExpr)) + condExpr++; + + if (*condExpr == '\0') { + Parse_Error(PARSE_WARNING, + "Missing right-hand-side of operator"); + goto done; + } + + rhs = CondGetString(doEval, &rhsQuoted, &rhsFree, FALSE); + if (!rhs) + goto done; + + if (rhsQuoted || lhsQuoted) { +do_string_compare: + if (((*op != '!') && (*op != '=')) || (op[1] != '=')) { + Parse_Error(PARSE_WARNING, + "String comparison operator should be either == or !="); + goto done; + } + + if (DEBUG(COND)) { + fprintf(debug_file, "lhs = \"%s\", rhs = \"%s\", op = %.2s\n", + lhs, rhs, op); + } + /* + * Null-terminate rhs and perform the comparison. + * t is set to the result. + */ + if (*op == '=') { + t = strcmp(lhs, rhs) == 0; + } else { + t = strcmp(lhs, rhs) != 0; + } + } else { + /* + * rhs is either a float or an integer. Convert both the + * lhs and the rhs to a double and compare the two. + */ + + if (!CondCvtArg(lhs, &left) || !CondCvtArg(rhs, &right)) + goto do_string_compare; + + if (DEBUG(COND)) { + fprintf(debug_file, "left = %f, right = %f, op = %.2s\n", left, + right, op); + } + switch(op[0]) { + case '!': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, + "Unknown operator"); + goto done; + } + t = (left != right); + break; + case '=': + if (op[1] != '=') { + Parse_Error(PARSE_WARNING, + "Unknown operator"); + goto done; + } + t = (left == right); + break; + case '<': + if (op[1] == '=') { + t = (left <= right); + } else { + t = (left < right); + } + break; + case '>': + if (op[1] == '=') { + t = (left >= right); + } else { + t = (left > right); + } + break; + } + } + +done: + free(lhsFree); + free(rhsFree); + return t; +} + +static int +get_mpt_arg(char **linePtr, char **argPtr, const char *func MAKE_ATTR_UNUSED) +{ + /* + * Use Var_Parse to parse the spec in parens and return + * TOK_TRUE if the resulting string is empty. + */ + int length; + void *freeIt; + char *val; + char *cp = *linePtr; + + /* We do all the work here and return the result as the length */ + *argPtr = NULL; + + val = Var_Parse(cp - 1, VAR_CMD, VARF_WANTRES, &length, &freeIt); + /* + * Advance *linePtr to beyond the closing ). Note that + * we subtract one because 'length' is calculated from 'cp - 1'. + */ + *linePtr = cp - 1 + length; + + if (val == var_Error) { + free(freeIt); + return -1; + } + + /* A variable is empty when it just contains spaces... 4/15/92, christos */ + while (isspace(*(unsigned char *)val)) + val++; + + /* + * For consistency with the other functions we can't generate the + * true/false here. + */ + length = *val ? 2 : 1; + free(freeIt); + return length; +} + +static Boolean +CondDoEmpty(int arglen, const char *arg MAKE_ATTR_UNUSED) +{ + return arglen == 1; +} + +static Token +compare_function(Boolean doEval) +{ + static const struct fn_def { + const char *fn_name; + int fn_name_len; + int (*fn_getarg)(char **, char **, const char *); + Boolean (*fn_proc)(int, const char *); + } fn_defs[] = { + { "defined", 7, CondGetArg, CondDoDefined }, + { "make", 4, CondGetArg, CondDoMake }, + { "exists", 6, CondGetArg, CondDoExists }, + { "empty", 5, get_mpt_arg, CondDoEmpty }, + { "target", 6, CondGetArg, CondDoTarget }, + { "commands", 8, CondGetArg, CondDoCommands }, + { NULL, 0, NULL, NULL }, + }; + const struct fn_def *fn_def; + Token t; + char *arg = NULL; + int arglen; + char *cp = condExpr; + char *cp1; + + for (fn_def = fn_defs; fn_def->fn_name != NULL; fn_def++) { + if (!istoken(cp, fn_def->fn_name, fn_def->fn_name_len)) + continue; + cp += fn_def->fn_name_len; + /* There can only be whitespace before the '(' */ + while (isspace(*(unsigned char *)cp)) + cp++; + if (*cp != '(') + break; + + arglen = fn_def->fn_getarg(&cp, &arg, fn_def->fn_name); + if (arglen <= 0) { + condExpr = cp; + return arglen < 0 ? TOK_ERROR : TOK_FALSE; + } + /* Evaluate the argument using the required function. */ + t = !doEval || fn_def->fn_proc(arglen, arg); + free(arg); + condExpr = cp; + return t; + } + + /* Push anything numeric through the compare expression */ + cp = condExpr; + if (isdigit((unsigned char)cp[0]) || strchr("+-", cp[0])) + return compare_expression(doEval); + + /* + * Most likely we have a naked token to apply the default function to. + * However ".if a == b" gets here when the "a" is unquoted and doesn't + * start with a '$'. This surprises people. + * If what follows the function argument is a '=' or '!' then the syntax + * would be invalid if we did "defined(a)" - so instead treat as an + * expression. + */ + arglen = CondGetArg(&cp, &arg, NULL); + for (cp1 = cp; isspace(*(unsigned char *)cp1); cp1++) + continue; + if (*cp1 == '=' || *cp1 == '!') + return compare_expression(doEval); + condExpr = cp; + + /* + * Evaluate the argument using the default function. + * This path always treats .if as .ifdef. To get here the character + * after .if must have been taken literally, so the argument cannot + * be empty - even if it contained a variable expansion. + */ + t = !doEval || if_info->defProc(arglen, arg) != if_info->doNot; + free(arg); + return t; +} + +static Token +CondToken(Boolean doEval) +{ + Token t; + + t = condPushBack; + if (t != TOK_NONE) { + condPushBack = TOK_NONE; + return t; + } + + while (*condExpr == ' ' || *condExpr == '\t') { + condExpr++; + } + + switch (*condExpr) { + + case '(': + condExpr++; + return TOK_LPAREN; + + case ')': + condExpr++; + return TOK_RPAREN; + + case '|': + if (condExpr[1] == '|') { + condExpr++; + } + condExpr++; + return TOK_OR; + + case '&': + if (condExpr[1] == '&') { + condExpr++; + } + condExpr++; + return TOK_AND; + + case '!': + condExpr++; + return TOK_NOT; + + case '#': + case '\n': + case '\0': + return TOK_EOF; + + case '"': + case '$': + return compare_expression(doEval); + + default: + return compare_function(doEval); + } +} + +/*- + *----------------------------------------------------------------------- + * CondT -- + * Parse a single term in the expression. This consists of a terminal + * symbol or TOK_NOT and a terminal symbol (not including the binary + * operators): + * T -> defined(variable) | make(target) | exists(file) | symbol + * T -> ! T | ( E ) + * + * Results: + * TOK_TRUE, TOK_FALSE or TOK_ERROR. + * + * Side Effects: + * Tokens are consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondT(Boolean doEval) +{ + Token t; + + t = CondToken(doEval); + + if (t == TOK_EOF) { + /* + * If we reached the end of the expression, the expression + * is malformed... + */ + t = TOK_ERROR; + } else if (t == TOK_LPAREN) { + /* + * T -> ( E ) + */ + t = CondE(doEval); + if (t != TOK_ERROR) { + if (CondToken(doEval) != TOK_RPAREN) { + t = TOK_ERROR; + } + } + } else if (t == TOK_NOT) { + t = CondT(doEval); + if (t == TOK_TRUE) { + t = TOK_FALSE; + } else if (t == TOK_FALSE) { + t = TOK_TRUE; + } + } + return (t); +} + +/*- + *----------------------------------------------------------------------- + * CondF -- + * Parse a conjunctive factor (nice name, wot?) + * F -> T && F | T + * + * Results: + * TOK_TRUE, TOK_FALSE or TOK_ERROR + * + * Side Effects: + * Tokens are consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondF(Boolean doEval) +{ + Token l, o; + + l = CondT(doEval); + if (l != TOK_ERROR) { + o = CondToken(doEval); + + if (o == TOK_AND) { + /* + * F -> T && F + * + * If T is TOK_FALSE, the whole thing will be TOK_FALSE, but we have to + * parse the r.h.s. anyway (to throw it away). + * If T is TOK_TRUE, the result is the r.h.s., be it an TOK_ERROR or no. + */ + if (l == TOK_TRUE) { + l = CondF(doEval); + } else { + (void)CondF(FALSE); + } + } else { + /* + * F -> T + */ + CondPushBack(o); + } + } + return (l); +} + +/*- + *----------------------------------------------------------------------- + * CondE -- + * Main expression production. + * E -> F || E | F + * + * Results: + * TOK_TRUE, TOK_FALSE or TOK_ERROR. + * + * Side Effects: + * Tokens are, of course, consumed. + * + *----------------------------------------------------------------------- + */ +static Token +CondE(Boolean doEval) +{ + Token l, o; + + l = CondF(doEval); + if (l != TOK_ERROR) { + o = CondToken(doEval); + + if (o == TOK_OR) { + /* + * E -> F || E + * + * A similar thing occurs for ||, except that here we make sure + * the l.h.s. is TOK_FALSE before we bother to evaluate the r.h.s. + * Once again, if l is TOK_FALSE, the result is the r.h.s. and once + * again if l is TOK_TRUE, we parse the r.h.s. to throw it away. + */ + if (l == TOK_FALSE) { + l = CondE(doEval); + } else { + (void)CondE(FALSE); + } + } else { + /* + * E -> F + */ + CondPushBack(o); + } + } + return (l); +} + +/*- + *----------------------------------------------------------------------- + * Cond_EvalExpression -- + * Evaluate an expression in the passed line. The expression + * consists of &&, ||, !, make(target), defined(variable) + * and parenthetical groupings thereof. + * + * Results: + * COND_PARSE if the condition was valid grammatically + * COND_INVALID if not a valid conditional. + * + * (*value) is set to the boolean value of the condition + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +int +Cond_EvalExpression(const struct If *info, char *line, Boolean *value, int eprint, Boolean strictLHS) +{ + static const struct If *dflt_info; + const struct If *sv_if_info = if_info; + char *sv_condExpr = condExpr; + Token sv_condPushBack = condPushBack; + int rval; + + lhsStrict = strictLHS; + + while (*line == ' ' || *line == '\t') + line++; + + if (info == NULL && (info = dflt_info) == NULL) { + /* Scan for the entry for .if - it can't be first */ + for (info = ifs; ; info++) + if (info->form[0] == 0) + break; + dflt_info = info; + } + assert(info != NULL); + + if_info = info; + condExpr = line; + condPushBack = TOK_NONE; + + rval = do_Cond_EvalExpression(value); + + if (rval == COND_INVALID && eprint) + Parse_Error(PARSE_FATAL, "Malformed conditional (%s)", line); + + if_info = sv_if_info; + condExpr = sv_condExpr; + condPushBack = sv_condPushBack; + + return rval; +} + +static int +do_Cond_EvalExpression(Boolean *value) +{ + + switch (CondE(TRUE)) { + case TOK_TRUE: + if (CondToken(TRUE) == TOK_EOF) { + *value = TRUE; + return COND_PARSE; + } + break; + case TOK_FALSE: + if (CondToken(TRUE) == TOK_EOF) { + *value = FALSE; + return COND_PARSE; + } + break; + default: + case TOK_ERROR: + break; + } + + return COND_INVALID; +} + + +/*- + *----------------------------------------------------------------------- + * Cond_Eval -- + * Evaluate the conditional in the passed line. The line + * looks like this: + * . + * where is any of if, ifmake, ifnmake, ifdef, + * ifndef, elif, elifmake, elifnmake, elifdef, elifndef + * and consists of &&, ||, !, make(target), defined(variable) + * and parenthetical groupings thereof. + * + * Input: + * line Line to parse + * + * Results: + * COND_PARSE if should parse lines after the conditional + * COND_SKIP if should skip lines after the conditional + * COND_INVALID if not a valid conditional. + * + * Side Effects: + * None. + * + * Note that the states IF_ACTIVE and ELSE_ACTIVE are only different in order + * to detect splurious .else lines (as are SKIP_TO_ELSE and SKIP_TO_ENDIF) + * otherwise .else could be treated as '.elif 1'. + * + *----------------------------------------------------------------------- + */ +int +Cond_Eval(char *line) +{ +#define MAXIF 128 /* maximum depth of .if'ing */ +#define MAXIF_BUMP 32 /* how much to grow by */ + enum if_states { + IF_ACTIVE, /* .if or .elif part active */ + ELSE_ACTIVE, /* .else part active */ + SEARCH_FOR_ELIF, /* searching for .elif/else to execute */ + SKIP_TO_ELSE, /* has been true, but not seen '.else' */ + SKIP_TO_ENDIF /* nothing else to execute */ + }; + static enum if_states *cond_state = NULL; + static unsigned int max_if_depth = MAXIF; + + const struct If *ifp; + Boolean isElif; + Boolean value; + int level; /* Level at which to report errors. */ + enum if_states state; + + level = PARSE_FATAL; + if (!cond_state) { + cond_state = bmake_malloc(max_if_depth * sizeof(*cond_state)); + cond_state[0] = IF_ACTIVE; + } + /* skip leading character (the '.') and any whitespace */ + for (line++; *line == ' ' || *line == '\t'; line++) + continue; + + /* Find what type of if we're dealing with. */ + if (line[0] == 'e') { + if (line[1] != 'l') { + if (!istoken(line + 1, "ndif", 4)) + return COND_INVALID; + /* End of conditional section */ + if (cond_depth == cond_min_depth) { + Parse_Error(level, "if-less endif"); + return COND_PARSE; + } + /* Return state for previous conditional */ + cond_depth--; + return cond_state[cond_depth] <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + } + + /* Quite likely this is 'else' or 'elif' */ + line += 2; + if (istoken(line, "se", 2)) { + /* It is else... */ + if (cond_depth == cond_min_depth) { + Parse_Error(level, "if-less else"); + return COND_PARSE; + } + + state = cond_state[cond_depth]; + switch (state) { + case SEARCH_FOR_ELIF: + state = ELSE_ACTIVE; + break; + case ELSE_ACTIVE: + case SKIP_TO_ENDIF: + Parse_Error(PARSE_WARNING, "extra else"); + /* FALLTHROUGH */ + default: + case IF_ACTIVE: + case SKIP_TO_ELSE: + state = SKIP_TO_ENDIF; + break; + } + cond_state[cond_depth] = state; + return state <= ELSE_ACTIVE ? COND_PARSE : COND_SKIP; + } + /* Assume for now it is an elif */ + isElif = TRUE; + } else + isElif = FALSE; + + if (line[0] != 'i' || line[1] != 'f') + /* Not an ifxxx or elifxxx line */ + return COND_INVALID; + + /* + * Figure out what sort of conditional it is -- what its default + * function is, etc. -- by looking in the table of valid "ifs" + */ + line += 2; + for (ifp = ifs; ; ifp++) { + if (ifp->form == NULL) + return COND_INVALID; + if (istoken(ifp->form, line, ifp->formlen)) { + line += ifp->formlen; + break; + } + } + + /* Now we know what sort of 'if' it is... */ + + if (isElif) { + if (cond_depth == cond_min_depth) { + Parse_Error(level, "if-less elif"); + return COND_PARSE; + } + state = cond_state[cond_depth]; + if (state == SKIP_TO_ENDIF || state == ELSE_ACTIVE) { + Parse_Error(PARSE_WARNING, "extra elif"); + cond_state[cond_depth] = SKIP_TO_ENDIF; + return COND_SKIP; + } + if (state != SEARCH_FOR_ELIF) { + /* Either just finished the 'true' block, or already SKIP_TO_ELSE */ + cond_state[cond_depth] = SKIP_TO_ELSE; + return COND_SKIP; + } + } else { + /* Normal .if */ + if (cond_depth + 1 >= max_if_depth) { + /* + * This is rare, but not impossible. + * In meta mode, dirdeps.mk (only runs at level 0) + * can need more than the default. + */ + max_if_depth += MAXIF_BUMP; + cond_state = bmake_realloc(cond_state, max_if_depth * + sizeof(*cond_state)); + } + state = cond_state[cond_depth]; + cond_depth++; + if (state > ELSE_ACTIVE) { + /* If we aren't parsing the data, treat as always false */ + cond_state[cond_depth] = SKIP_TO_ELSE; + return COND_SKIP; + } + } + + /* And evaluate the conditional expresssion */ + if (Cond_EvalExpression(ifp, line, &value, 1, TRUE) == COND_INVALID) { + /* Syntax error in conditional, error message already output. */ + /* Skip everything to matching .endif */ + cond_state[cond_depth] = SKIP_TO_ELSE; + return COND_SKIP; + } + + if (!value) { + cond_state[cond_depth] = SEARCH_FOR_ELIF; + return COND_SKIP; + } + cond_state[cond_depth] = IF_ACTIVE; + return COND_PARSE; +} + + + +/*- + *----------------------------------------------------------------------- + * Cond_End -- + * Make sure everything's clean at the end of a makefile. + * + * Results: + * None. + * + * Side Effects: + * Parse_Error will be called if open conditionals are around. + * + *----------------------------------------------------------------------- + */ +void +Cond_restore_depth(unsigned int saved_depth) +{ + int open_conds = cond_depth - cond_min_depth; + + if (open_conds != 0 || saved_depth > cond_depth) { + Parse_Error(PARSE_FATAL, "%d open conditional%s", open_conds, + open_conds == 1 ? "" : "s"); + cond_depth = cond_min_depth; + } + + cond_min_depth = saved_depth; +} + +unsigned int +Cond_save_depth(void) +{ + int depth = cond_min_depth; + + cond_min_depth = cond_depth; + return depth; +} diff --git a/usr.bin/make/config.h b/usr.bin/make/config.h new file mode 100644 index 0000000..06af09c --- /dev/null +++ b/usr.bin/make/config.h @@ -0,0 +1,160 @@ +/* $NetBSD: config.h,v 1.21 2012/03/31 00:12:24 christos Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)config.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)config.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * DEFMAXJOBS + * DEFMAXLOCAL + * These control the default concurrency. On no occasion will more + * than DEFMAXJOBS targets be created at once (locally or remotely) + * DEFMAXLOCAL is the highest number of targets which will be + * created on the local machine at once. Note that if you set this + * to 0, nothing will ever happen... + */ +#define DEFMAXJOBS 4 +#define DEFMAXLOCAL 1 + +/* + * INCLUDES + * LIBRARIES + * These control the handling of the .INCLUDES and .LIBS variables. + * If INCLUDES is defined, the .INCLUDES variable will be filled + * from the search paths of those suffixes which are marked by + * .INCLUDES dependency lines. Similarly for LIBRARIES and .LIBS + * See suff.c for more details. + */ +#define INCLUDES +#define LIBRARIES + +/* + * LIBSUFF + * Is the suffix used to denote libraries and is used by the Suff module + * to find the search path on which to seek any -l targets. + * + * RECHECK + * If defined, Make_Update will check a target for its current + * modification time after it has been re-made, setting it to the + * starting time of the make only if the target still doesn't exist. + * Unfortunately, under NFS the modification time often doesn't + * get updated in time, so a target will appear to not have been + * re-made, causing later targets to appear up-to-date. On systems + * that don't have this problem, you should defined this. Under + * NFS you probably should not, unless you aren't exporting jobs. + */ +#define LIBSUFF ".a" +#define RECHECK + +/* + * POSIX + * Adhere to the POSIX 1003.2 draft for the make(1) program. + * - Use MAKEFLAGS instead of MAKE to pick arguments from the + * environment. + * - Allow empty command lines if starting with tab. + */ +#define POSIX + +/* + * SYSVINCLUDE + * Recognize system V like include directives [include "filename"] + * SYSVVARSUB + * Recognize system V like ${VAR:x=y} variable substitutions + */ +#define SYSVINCLUDE +#define SYSVVARSUB + +/* + * GMAKEEXPORT + * Recognize gmake like variable export directives [export =] + */ +#define GMAKEEXPORT + +/* + * SUNSHCMD + * Recognize SunOS and Solaris: + * VAR :sh= CMD # Assign VAR to the command substitution of CMD + * ${VAR:sh} # Return the command substitution of the value + * # of ${VAR} + */ +#define SUNSHCMD + +/* + * USE_IOVEC + * We have writev(2) + */ +#define USE_IOVEC + +#if defined(MAKE_NATIVE) && !defined(__ELF__) +# ifndef RANLIBMAG +# define RANLIBMAG "__.SYMDEF" +# endif +#endif diff --git a/usr.bin/make/dir.c b/usr.bin/make/dir.c new file mode 100644 index 0000000..0062ac0 --- /dev/null +++ b/usr.bin/make/dir.c @@ -0,0 +1,1849 @@ +/* $NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)dir.c 8.2 (Berkeley) 1/2/94"; +#else +__RCSID("$NetBSD: dir.c,v 1.73 2018/07/12 18:03:31 christos Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * dir.c -- + * Directory searching using wildcards and/or normal names... + * Used both for source wildcarding in the Makefile and for finding + * implicit sources. + * + * The interface for this module is: + * Dir_Init Initialize the module. + * + * Dir_InitCur Set the cur Path. + * + * Dir_InitDot Set the dot Path. + * + * Dir_End Cleanup the module. + * + * Dir_SetPATH Set ${.PATH} to reflect state of dirSearchPath. + * + * Dir_HasWildcards Returns TRUE if the name given it needs to + * be wildcard-expanded. + * + * Dir_Expand Given a pattern and a path, return a Lst of names + * which match the pattern on the search path. + * + * Dir_FindFile Searches for a file on a given search path. + * If it exists, the entire path is returned. + * Otherwise NULL is returned. + * + * Dir_FindHereOrAbove Search for a path in the current directory and + * then all the directories above it in turn until + * the path is found or we reach the root ("/"). + * + * Dir_MTime Return the modification time of a node. The file + * is searched for along the default search path. + * The path and mtime fields of the node are filled + * in. + * + * Dir_AddDir Add a directory to a search path. + * + * Dir_MakeFlags Given a search path and a command flag, create + * a string with each of the directories in the path + * preceded by the command flag and all of them + * separated by a space. + * + * Dir_Destroy Destroy an element of a search path. Frees up all + * things that can be freed for the element as long + * as the element is no longer referenced by any other + * search path. + * Dir_ClearPath Resets a search path to the empty list. + * + * For debugging: + * Dir_PrintDirectories Print stats about the directory cache. + */ + +#include +#include + +#include +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" + +/* + * A search path consists of a Lst of Path structures. A Path structure + * has in it the name of the directory and a hash table of all the files + * in the directory. This is used to cut down on the number of system + * calls necessary to find implicit dependents and their like. Since + * these searches are made before any actions are taken, we need not + * worry about the directory changing due to creation commands. If this + * hampers the style of some makefiles, they must be changed. + * + * A list of all previously-read directories is kept in the + * openDirectories Lst. This list is checked first before a directory + * is opened. + * + * The need for the caching of whole directories is brought about by + * the multi-level transformation code in suff.c, which tends to search + * for far more files than regular make does. In the initial + * implementation, the amount of time spent performing "stat" calls was + * truly astronomical. The problem with hashing at the start is, + * of course, that pmake doesn't then detect changes to these directories + * during the course of the make. Three possibilities suggest themselves: + * + * 1) just use stat to test for a file's existence. As mentioned + * above, this is very inefficient due to the number of checks + * engendered by the multi-level transformation code. + * 2) use readdir() and company to search the directories, keeping + * them open between checks. I have tried this and while it + * didn't slow down the process too much, it could severely + * affect the amount of parallelism available as each directory + * open would take another file descriptor out of play for + * handling I/O for another job. Given that it is only recently + * that UNIX OS's have taken to allowing more than 20 or 32 + * file descriptors for a process, this doesn't seem acceptable + * to me. + * 3) record the mtime of the directory in the Path structure and + * verify the directory hasn't changed since the contents were + * hashed. This will catch the creation or deletion of files, + * but not the updating of files. However, since it is the + * creation and deletion that is the problem, this could be + * a good thing to do. Unfortunately, if the directory (say ".") + * were fairly large and changed fairly frequently, the constant + * rehashing could seriously degrade performance. It might be + * good in such cases to keep track of the number of rehashes + * and if the number goes over a (small) limit, resort to using + * stat in its place. + * + * An additional thing to consider is that pmake is used primarily + * to create C programs and until recently pcc-based compilers refused + * to allow you to specify where the resulting object file should be + * placed. This forced all objects to be created in the current + * directory. This isn't meant as a full excuse, just an explanation of + * some of the reasons for the caching used here. + * + * One more note: the location of a target's file is only performed + * on the downward traversal of the graph and then only for terminal + * nodes in the graph. This could be construed as wrong in some cases, + * but prevents inadvertent modification of files when the "installed" + * directory for a file is provided in the search path. + * + * Another data structure maintained by this module is an mtime + * cache used when the searching of cached directories fails to find + * a file. In the past, Dir_FindFile would simply perform an access() + * call in such a case to determine if the file could be found using + * just the name given. When this hit, however, all that was gained + * was the knowledge that the file existed. Given that an access() is + * essentially a stat() without the copyout() call, and that the same + * filesystem overhead would have to be incurred in Dir_MTime, it made + * sense to replace the access() with a stat() and record the mtime + * in a cache for when Dir_MTime was actually called. + */ + +Lst dirSearchPath; /* main search path */ + +static Lst openDirectories; /* the list of all open directories */ + +/* + * Variables for gathering statistics on the efficiency of the hashing + * mechanism. + */ +static int hits, /* Found in directory cache */ + misses, /* Sad, but not evil misses */ + nearmisses, /* Found under search path */ + bigmisses; /* Sought by itself */ + +static Path *dot; /* contents of current directory */ +static Path *cur; /* contents of current directory, if not dot */ +static Path *dotLast; /* a fake path entry indicating we need to + * look for . last */ +static Hash_Table mtimes; /* Results of doing a last-resort stat in + * Dir_FindFile -- if we have to go to the + * system to find the file, we might as well + * have its mtime on record. XXX: If this is done + * way early, there's a chance other rules will + * have already updated the file, in which case + * we'll update it again. Generally, there won't + * be two rules to update a single file, so this + * should be ok, but... */ + +static Hash_Table lmtimes; /* same as mtimes but for lstat */ + +static int DirFindName(const void *, const void *); +static int DirMatchFiles(const char *, Path *, Lst); +static void DirExpandCurly(const char *, const char *, Lst, Lst); +static void DirExpandInt(const char *, Lst, Lst); +static int DirPrintWord(void *, void *); +static int DirPrintDir(void *, void *); +static char *DirLookup(Path *, const char *, const char *, Boolean); +static char *DirLookupSubdir(Path *, const char *); +static char *DirFindDot(Boolean, const char *, const char *); +static char *DirLookupAbs(Path *, const char *, const char *); + + +/* + * We use stat(2) a lot, cache the results + * mtime and mode are all we care about. + */ +struct cache_st { + time_t mtime; + mode_t mode; +}; + +/* minimize changes below */ +#define CST_LSTAT 1 +#define CST_UPDATE 2 + +static int +cached_stats(Hash_Table *htp, const char *pathname, struct stat *st, int flags) +{ + Hash_Entry *entry; + struct cache_st *cst; + int rc; + + if (!pathname || !pathname[0]) + return -1; + + entry = Hash_FindEntry(htp, pathname); + + if (entry && (flags & CST_UPDATE) == 0) { + cst = entry->clientPtr; + + memset(st, 0, sizeof(*st)); + st->st_mtime = cst->mtime; + st->st_mode = cst->mode; + if (DEBUG(DIR)) { + fprintf(debug_file, "Using cached time %s for %s\n", + Targ_FmtTime(st->st_mtime), pathname); + } + return 0; + } + + rc = (flags & CST_LSTAT) ? lstat(pathname, st) : stat(pathname, st); + if (rc == -1) + return -1; + + if (st->st_mtime == 0) + st->st_mtime = 1; /* avoid confusion with missing file */ + + if (!entry) + entry = Hash_CreateEntry(htp, pathname, NULL); + if (!entry->clientPtr) + entry->clientPtr = bmake_malloc(sizeof(*cst)); + cst = entry->clientPtr; + cst->mtime = st->st_mtime; + cst->mode = st->st_mode; + if (DEBUG(DIR)) { + fprintf(debug_file, " Caching %s for %s\n", + Targ_FmtTime(st->st_mtime), pathname); + } + + return 0; +} + +int +cached_stat(const char *pathname, void *st) +{ + return cached_stats(&mtimes, pathname, st, 0); +} + +int +cached_lstat(const char *pathname, void *st) +{ + return cached_stats(&lmtimes, pathname, st, CST_LSTAT); +} + +/*- + *----------------------------------------------------------------------- + * Dir_Init -- + * initialize things for this module + * + * Results: + * none + * + * Side Effects: + * some directories may be opened. + *----------------------------------------------------------------------- + */ +void +Dir_Init(const char *cdname) +{ + if (!cdname) { + dirSearchPath = Lst_Init(FALSE); + openDirectories = Lst_Init(FALSE); + Hash_InitTable(&mtimes, 0); + Hash_InitTable(&lmtimes, 0); + return; + } + Dir_InitCur(cdname); + + dotLast = bmake_malloc(sizeof(Path)); + dotLast->refCount = 1; + dotLast->hits = 0; + dotLast->name = bmake_strdup(".DOTLAST"); + Hash_InitTable(&dotLast->files, -1); +} + +/* + * Called by Dir_Init() and whenever .CURDIR is assigned to. + */ +void +Dir_InitCur(const char *cdname) +{ + Path *p; + + if (cdname != NULL) { + /* + * Our build directory is not the same as our source directory. + * Keep this one around too. + */ + if ((p = Dir_AddDir(NULL, cdname))) { + p->refCount += 1; + if (cur && cur != p) { + /* + * We've been here before, cleanup. + */ + cur->refCount -= 1; + Dir_Destroy(cur); + } + cur = p; + } + } +} + +/*- + *----------------------------------------------------------------------- + * Dir_InitDot -- + * (re)initialize "dot" (current/object directory) path hash + * + * Results: + * none + * + * Side Effects: + * some directories may be opened. + *----------------------------------------------------------------------- + */ +void +Dir_InitDot(void) +{ + if (dot != NULL) { + LstNode ln; + + /* Remove old entry from openDirectories, but do not destroy. */ + ln = Lst_Member(openDirectories, dot); + (void)Lst_Remove(openDirectories, ln); + } + + dot = Dir_AddDir(NULL, "."); + + if (dot == NULL) { + Error("Cannot open `.' (%s)", strerror(errno)); + exit(1); + } + + /* + * We always need to have dot around, so we increment its reference count + * to make sure it's not destroyed. + */ + dot->refCount += 1; + Dir_SetPATH(); /* initialize */ +} + +/*- + *----------------------------------------------------------------------- + * Dir_End -- + * cleanup things for this module + * + * Results: + * none + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +void +Dir_End(void) +{ +#ifdef CLEANUP + if (cur) { + cur->refCount -= 1; + Dir_Destroy(cur); + } + dot->refCount -= 1; + dotLast->refCount -= 1; + Dir_Destroy(dotLast); + Dir_Destroy(dot); + Dir_ClearPath(dirSearchPath); + Lst_Destroy(dirSearchPath, NULL); + Dir_ClearPath(openDirectories); + Lst_Destroy(openDirectories, NULL); + Hash_DeleteTable(&mtimes); +#endif +} + +/* + * We want ${.PATH} to indicate the order in which we will actually + * search, so we rebuild it after any .PATH: target. + * This is the simplest way to deal with the effect of .DOTLAST. + */ +void +Dir_SetPATH(void) +{ + LstNode ln; /* a list element */ + Path *p; + Boolean hasLastDot = FALSE; /* true we should search dot last */ + + Var_Delete(".PATH", VAR_GLOBAL); + + if (Lst_Open(dirSearchPath) == SUCCESS) { + if ((ln = Lst_First(dirSearchPath)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) { + hasLastDot = TRUE; + Var_Append(".PATH", dotLast->name, VAR_GLOBAL); + } + } + + if (!hasLastDot) { + if (dot) + Var_Append(".PATH", dot->name, VAR_GLOBAL); + if (cur) + Var_Append(".PATH", cur->name, VAR_GLOBAL); + } + + while ((ln = Lst_Next(dirSearchPath)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) + continue; + if (p == dot && hasLastDot) + continue; + Var_Append(".PATH", p->name, VAR_GLOBAL); + } + + if (hasLastDot) { + if (dot) + Var_Append(".PATH", dot->name, VAR_GLOBAL); + if (cur) + Var_Append(".PATH", cur->name, VAR_GLOBAL); + } + Lst_Close(dirSearchPath); + } +} + +/*- + *----------------------------------------------------------------------- + * DirFindName -- + * See if the Path structure describes the same directory as the + * given one by comparing their names. Called from Dir_AddDir via + * Lst_Find when searching the list of open directories. + * + * Input: + * p Current name + * dname Desired name + * + * Results: + * 0 if it is the same. Non-zero otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +DirFindName(const void *p, const void *dname) +{ + return (strcmp(((const Path *)p)->name, dname)); +} + +/*- + *----------------------------------------------------------------------- + * Dir_HasWildcards -- + * see if the given name has any wildcard characters in it + * be careful not to expand unmatching brackets or braces. + * XXX: This code is not 100% correct. ([^]] fails etc.) + * I really don't think that make(1) should be expanding + * patterns, because then you have to set a mechanism for + * escaping the expansion! + * + * Input: + * name name to check + * + * Results: + * returns TRUE if the word should be expanded, FALSE otherwise + * + * Side Effects: + * none + *----------------------------------------------------------------------- + */ +Boolean +Dir_HasWildcards(char *name) +{ + char *cp; + int wild = 0, brace = 0, bracket = 0; + + for (cp = name; *cp; cp++) { + switch(*cp) { + case '{': + brace++; + wild = 1; + break; + case '}': + brace--; + break; + case '[': + bracket++; + wild = 1; + break; + case ']': + bracket--; + break; + case '?': + case '*': + wild = 1; + break; + default: + break; + } + } + return wild && bracket == 0 && brace == 0; +} + +/*- + *----------------------------------------------------------------------- + * DirMatchFiles -- + * Given a pattern and a Path structure, see if any files + * match the pattern and add their names to the 'expansions' list if + * any do. This is incomplete -- it doesn't take care of patterns like + * src / *src / *.c properly (just *.c on any of the directories), but it + * will do for now. + * + * Input: + * pattern Pattern to look for + * p Directory to search + * expansion Place to store the results + * + * Results: + * Always returns 0 + * + * Side Effects: + * File names are added to the expansions lst. The directory will be + * fully hashed when this is done. + *----------------------------------------------------------------------- + */ +static int +DirMatchFiles(const char *pattern, Path *p, Lst expansions) +{ + Hash_Search search; /* Index into the directory's table */ + Hash_Entry *entry; /* Current entry in the table */ + Boolean isDot; /* TRUE if the directory being searched is . */ + + isDot = (*p->name == '.' && p->name[1] == '\0'); + + for (entry = Hash_EnumFirst(&p->files, &search); + entry != NULL; + entry = Hash_EnumNext(&search)) + { + /* + * See if the file matches the given pattern. Note we follow the UNIX + * convention that dot files will only be found if the pattern + * begins with a dot (note also that as a side effect of the hashing + * scheme, .* won't match . or .. since they aren't hashed). + */ + if (Str_Match(entry->name, pattern) && + ((entry->name[0] != '.') || + (pattern[0] == '.'))) + { + (void)Lst_AtEnd(expansions, + (isDot ? bmake_strdup(entry->name) : + str_concat(p->name, entry->name, + STR_ADDSLASH))); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * DirExpandCurly -- + * Expand curly braces like the C shell. Does this recursively. + * Note the special case: if after the piece of the curly brace is + * done there are no wildcard characters in the result, the result is + * placed on the list WITHOUT CHECKING FOR ITS EXISTENCE. + * + * Input: + * word Entire word to expand + * brace First curly brace in it + * path Search path to use + * expansions Place to store the expansions + * + * Results: + * None. + * + * Side Effects: + * The given list is filled with the expansions... + * + *----------------------------------------------------------------------- + */ +static void +DirExpandCurly(const char *word, const char *brace, Lst path, Lst expansions) +{ + const char *end; /* Character after the closing brace */ + const char *cp; /* Current position in brace clause */ + const char *start; /* Start of current piece of brace clause */ + int bracelevel; /* Number of braces we've seen. If we see a + * right brace when this is 0, we've hit the + * end of the clause. */ + char *file; /* Current expansion */ + int otherLen; /* The length of the other pieces of the + * expansion (chars before and after the + * clause in 'word') */ + char *cp2; /* Pointer for checking for wildcards in + * expansion before calling Dir_Expand */ + + start = brace+1; + + /* + * Find the end of the brace clause first, being wary of nested brace + * clauses. + */ + for (end = start, bracelevel = 0; *end != '\0'; end++) { + if (*end == '{') { + bracelevel++; + } else if ((*end == '}') && (bracelevel-- == 0)) { + break; + } + } + if (*end == '\0') { + Error("Unterminated {} clause \"%s\"", start); + return; + } else { + end++; + } + otherLen = brace - word + strlen(end); + + for (cp = start; cp < end; cp++) { + /* + * Find the end of this piece of the clause. + */ + bracelevel = 0; + while (*cp != ',') { + if (*cp == '{') { + bracelevel++; + } else if ((*cp == '}') && (bracelevel-- <= 0)) { + break; + } + cp++; + } + /* + * Allocate room for the combination and install the three pieces. + */ + file = bmake_malloc(otherLen + cp - start + 1); + if (brace != word) { + strncpy(file, word, brace-word); + } + if (cp != start) { + strncpy(&file[brace-word], start, cp-start); + } + strcpy(&file[(brace-word)+(cp-start)], end); + + /* + * See if the result has any wildcards in it. If we find one, call + * Dir_Expand right away, telling it to place the result on our list + * of expansions. + */ + for (cp2 = file; *cp2 != '\0'; cp2++) { + switch(*cp2) { + case '*': + case '?': + case '{': + case '[': + Dir_Expand(file, path, expansions); + goto next; + } + } + if (*cp2 == '\0') { + /* + * Hit the end w/o finding any wildcards, so stick the expansion + * on the end of the list. + */ + (void)Lst_AtEnd(expansions, file); + } else { + next: + free(file); + } + start = cp+1; + } +} + + +/*- + *----------------------------------------------------------------------- + * DirExpandInt -- + * Internal expand routine. Passes through the directories in the + * path one by one, calling DirMatchFiles for each. NOTE: This still + * doesn't handle patterns in directories... + * + * Input: + * word Word to expand + * path Path on which to look + * expansions Place to store the result + * + * Results: + * None. + * + * Side Effects: + * Things are added to the expansions list. + * + *----------------------------------------------------------------------- + */ +static void +DirExpandInt(const char *word, Lst path, Lst expansions) +{ + LstNode ln; /* Current node */ + Path *p; /* Directory in the node */ + + if (Lst_Open(path) == SUCCESS) { + while ((ln = Lst_Next(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + DirMatchFiles(word, p, expansions); + } + Lst_Close(path); + } +} + +/*- + *----------------------------------------------------------------------- + * DirPrintWord -- + * Print a word in the list of expansions. Callback for Dir_Expand + * when DEBUG(DIR), via Lst_ForEach. + * + * Results: + * === 0 + * + * Side Effects: + * The passed word is printed, followed by a space. + * + *----------------------------------------------------------------------- + */ +static int +DirPrintWord(void *word, void *dummy MAKE_ATTR_UNUSED) +{ + fprintf(debug_file, "%s ", (char *)word); + + return 0; +} + +/*- + *----------------------------------------------------------------------- + * Dir_Expand -- + * Expand the given word into a list of words by globbing it looking + * in the directories on the given search path. + * + * Input: + * word the word to expand + * path the list of directories in which to find the + * resulting files + * expansions the list on which to place the results + * + * Results: + * A list of words consisting of the files which exist along the search + * path matching the given pattern. + * + * Side Effects: + * Directories may be opened. Who knows? + *----------------------------------------------------------------------- + */ +void +Dir_Expand(const char *word, Lst path, Lst expansions) +{ + const char *cp; + + if (DEBUG(DIR)) { + fprintf(debug_file, "Expanding \"%s\"... ", word); + } + + cp = strchr(word, '{'); + if (cp) { + DirExpandCurly(word, cp, path, expansions); + } else { + cp = strchr(word, '/'); + if (cp) { + /* + * The thing has a directory component -- find the first wildcard + * in the string. + */ + for (cp = word; *cp; cp++) { + if (*cp == '?' || *cp == '[' || *cp == '*' || *cp == '{') { + break; + } + } + if (*cp == '{') { + /* + * This one will be fun. + */ + DirExpandCurly(word, cp, path, expansions); + return; + } else if (*cp != '\0') { + /* + * Back up to the start of the component + */ + char *dirpath; + + while (cp > word && *cp != '/') { + cp--; + } + if (cp != word) { + char sc; + /* + * If the glob isn't in the first component, try and find + * all the components up to the one with a wildcard. + */ + sc = cp[1]; + ((char *)UNCONST(cp))[1] = '\0'; + dirpath = Dir_FindFile(word, path); + ((char *)UNCONST(cp))[1] = sc; + /* + * dirpath is null if can't find the leading component + * XXX: Dir_FindFile won't find internal components. + * i.e. if the path contains ../Etc/Object and we're + * looking for Etc, it won't be found. Ah well. + * Probably not important. + */ + if (dirpath != NULL) { + char *dp = &dirpath[strlen(dirpath) - 1]; + if (*dp == '/') + *dp = '\0'; + path = Lst_Init(FALSE); + (void)Dir_AddDir(path, dirpath); + DirExpandInt(cp+1, path, expansions); + Lst_Destroy(path, NULL); + } + } else { + /* + * Start the search from the local directory + */ + DirExpandInt(word, path, expansions); + } + } else { + /* + * Return the file -- this should never happen. + */ + DirExpandInt(word, path, expansions); + } + } else { + /* + * First the files in dot + */ + DirMatchFiles(word, dot, expansions); + + /* + * Then the files in every other directory on the path. + */ + DirExpandInt(word, path, expansions); + } + } + if (DEBUG(DIR)) { + Lst_ForEach(expansions, DirPrintWord, NULL); + fprintf(debug_file, "\n"); + } +} + +/*- + *----------------------------------------------------------------------- + * DirLookup -- + * Find if the file with the given name exists in the given path. + * + * Results: + * The path to the file or NULL. This path is guaranteed to be in a + * different part of memory than name and so may be safely free'd. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +static char * +DirLookup(Path *p, const char *name MAKE_ATTR_UNUSED, const char *cp, + Boolean hasSlash MAKE_ATTR_UNUSED) +{ + char *file; /* the current filename to check */ + + if (DEBUG(DIR)) { + fprintf(debug_file, " %s ...\n", p->name); + } + + if (Hash_FindEntry(&p->files, cp) == NULL) + return NULL; + + file = str_concat(p->name, cp, STR_ADDSLASH); + if (DEBUG(DIR)) { + fprintf(debug_file, " returning %s\n", file); + } + p->hits += 1; + hits += 1; + return file; +} + + +/*- + *----------------------------------------------------------------------- + * DirLookupSubdir -- + * Find if the file with the given name exists in the given path. + * + * Results: + * The path to the file or NULL. This path is guaranteed to be in a + * different part of memory than name and so may be safely free'd. + * + * Side Effects: + * If the file is found, it is added in the modification times hash + * table. + *----------------------------------------------------------------------- + */ +static char * +DirLookupSubdir(Path *p, const char *name) +{ + struct stat stb; /* Buffer for stat, if necessary */ + char *file; /* the current filename to check */ + + if (p != dot) { + file = str_concat(p->name, name, STR_ADDSLASH); + } else { + /* + * Checking in dot -- DON'T put a leading ./ on the thing. + */ + file = bmake_strdup(name); + } + + if (DEBUG(DIR)) { + fprintf(debug_file, "checking %s ...\n", file); + } + + if (cached_stat(file, &stb) == 0) { + nearmisses += 1; + return (file); + } + free(file); + return NULL; +} + +/*- + *----------------------------------------------------------------------- + * DirLookupAbs -- + * Find if the file with the given name exists in the given path. + * + * Results: + * The path to the file, the empty string or NULL. If the file is + * the empty string, the search should be terminated. + * This path is guaranteed to be in a different part of memory + * than name and so may be safely free'd. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +static char * +DirLookupAbs(Path *p, const char *name, const char *cp) +{ + char *p1; /* pointer into p->name */ + const char *p2; /* pointer into name */ + + if (DEBUG(DIR)) { + fprintf(debug_file, " %s ...\n", p->name); + } + + /* + * If the file has a leading path component and that component + * exactly matches the entire name of the current search + * directory, we can attempt another cache lookup. And if we don't + * have a hit, we can safely assume the file does not exist at all. + */ + for (p1 = p->name, p2 = name; *p1 && *p1 == *p2; p1++, p2++) { + continue; + } + if (*p1 != '\0' || p2 != cp - 1) { + return NULL; + } + + if (Hash_FindEntry(&p->files, cp) == NULL) { + if (DEBUG(DIR)) { + fprintf(debug_file, " must be here but isn't -- returning\n"); + } + /* Return empty string: terminates search */ + return bmake_strdup(""); + } + + p->hits += 1; + hits += 1; + if (DEBUG(DIR)) { + fprintf(debug_file, " returning %s\n", name); + } + return (bmake_strdup(name)); +} + +/*- + *----------------------------------------------------------------------- + * DirFindDot -- + * Find the file given on "." or curdir + * + * Results: + * The path to the file or NULL. This path is guaranteed to be in a + * different part of memory than name and so may be safely free'd. + * + * Side Effects: + * Hit counts change + *----------------------------------------------------------------------- + */ +static char * +DirFindDot(Boolean hasSlash MAKE_ATTR_UNUSED, const char *name, const char *cp) +{ + + if (Hash_FindEntry(&dot->files, cp) != NULL) { + if (DEBUG(DIR)) { + fprintf(debug_file, " in '.'\n"); + } + hits += 1; + dot->hits += 1; + return (bmake_strdup(name)); + } + if (cur && + Hash_FindEntry(&cur->files, cp) != NULL) { + if (DEBUG(DIR)) { + fprintf(debug_file, " in ${.CURDIR} = %s\n", cur->name); + } + hits += 1; + cur->hits += 1; + return str_concat(cur->name, cp, STR_ADDSLASH); + } + + return NULL; +} + +/*- + *----------------------------------------------------------------------- + * Dir_FindFile -- + * Find the file with the given name along the given search path. + * + * Input: + * name the file to find + * path the Lst of directories to search + * + * Results: + * The path to the file or NULL. This path is guaranteed to be in a + * different part of memory than name and so may be safely free'd. + * + * Side Effects: + * If the file is found in a directory which is not on the path + * already (either 'name' is absolute or it is a relative path + * [ dir1/.../dirn/file ] which exists below one of the directories + * already on the search path), its directory is added to the end + * of the path on the assumption that there will be more files in + * that directory later on. Sometimes this is true. Sometimes not. + *----------------------------------------------------------------------- + */ +char * +Dir_FindFile(const char *name, Lst path) +{ + LstNode ln; /* a list element */ + char *file; /* the current filename to check */ + Path *p; /* current path member */ + const char *cp; /* Terminal name of file */ + Boolean hasLastDot = FALSE; /* true we should search dot last */ + Boolean hasSlash; /* true if 'name' contains a / */ + struct stat stb; /* Buffer for stat, if necessary */ + const char *trailing_dot = "."; + + /* + * Find the final component of the name and note whether it has a + * slash in it (the name, I mean) + */ + cp = strrchr(name, '/'); + if (cp) { + hasSlash = TRUE; + cp += 1; + } else { + hasSlash = FALSE; + cp = name; + } + + if (DEBUG(DIR)) { + fprintf(debug_file, "Searching for %s ...", name); + } + + if (Lst_Open(path) == FAILURE) { + if (DEBUG(DIR)) { + fprintf(debug_file, "couldn't open path, file not found\n"); + } + misses += 1; + return NULL; + } + + if ((ln = Lst_First(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) { + hasLastDot = TRUE; + if (DEBUG(DIR)) + fprintf(debug_file, "[dot last]..."); + } + } + if (DEBUG(DIR)) { + fprintf(debug_file, "\n"); + } + + /* + * If there's no leading directory components or if the leading + * directory component is exactly `./', consult the cached contents + * of each of the directories on the search path. + */ + if (!hasSlash || (cp - name == 2 && *name == '.')) { + /* + * We look through all the directories on the path seeking one which + * contains the final component of the given name. If such a beast + * is found, we concatenate the directory name and the final + * component and return the resulting string. If we don't find any + * such thing, we go on to phase two... + * + * No matter what, we always look for the file in the current + * directory before anywhere else (unless we found the magic + * DOTLAST path, in which case we search it last) and we *do not* + * add the ./ to it if it exists. + * This is so there are no conflicts between what the user + * specifies (fish.c) and what pmake finds (./fish.c). + */ + if (!hasLastDot && + (file = DirFindDot(hasSlash, name, cp)) != NULL) { + Lst_Close(path); + return file; + } + + while ((ln = Lst_Next(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) + continue; + if ((file = DirLookup(p, name, cp, hasSlash)) != NULL) { + Lst_Close(path); + return file; + } + } + + if (hasLastDot && + (file = DirFindDot(hasSlash, name, cp)) != NULL) { + Lst_Close(path); + return file; + } + } + Lst_Close(path); + + /* + * We didn't find the file on any directory in the search path. + * If the name doesn't contain a slash, that means it doesn't exist. + * If it *does* contain a slash, however, there is still hope: it + * could be in a subdirectory of one of the members of the search + * path. (eg. /usr/include and sys/types.h. The above search would + * fail to turn up types.h in /usr/include, but it *is* in + * /usr/include/sys/types.h). + * [ This no longer applies: If we find such a beast, we assume there + * will be more (what else can we assume?) and add all but the last + * component of the resulting name onto the search path (at the + * end).] + * This phase is only performed if the file is *not* absolute. + */ + if (!hasSlash) { + if (DEBUG(DIR)) { + fprintf(debug_file, " failed.\n"); + } + misses += 1; + return NULL; + } + + if (*cp == '\0') { + /* we were given a trailing "/" */ + cp = trailing_dot; + } + + if (name[0] != '/') { + Boolean checkedDot = FALSE; + + if (DEBUG(DIR)) { + fprintf(debug_file, " Trying subdirectories...\n"); + } + + if (!hasLastDot) { + if (dot) { + checkedDot = TRUE; + if ((file = DirLookupSubdir(dot, name)) != NULL) + return file; + } + if (cur && (file = DirLookupSubdir(cur, name)) != NULL) + return file; + } + + (void)Lst_Open(path); + while ((ln = Lst_Next(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) + continue; + if (p == dot) { + if (checkedDot) + continue; + checkedDot = TRUE; + } + if ((file = DirLookupSubdir(p, name)) != NULL) { + Lst_Close(path); + return file; + } + } + Lst_Close(path); + + if (hasLastDot) { + if (dot && !checkedDot) { + checkedDot = TRUE; + if ((file = DirLookupSubdir(dot, name)) != NULL) + return file; + } + if (cur && (file = DirLookupSubdir(cur, name)) != NULL) + return file; + } + + if (checkedDot) { + /* + * Already checked by the given name, since . was in the path, + * so no point in proceeding... + */ + if (DEBUG(DIR)) { + fprintf(debug_file, " Checked . already, returning NULL\n"); + } + return NULL; + } + + } else { /* name[0] == '/' */ + + /* + * For absolute names, compare directory path prefix against the + * the directory path of each member on the search path for an exact + * match. If we have an exact match on any member of the search path, + * use the cached contents of that member to lookup the final file + * component. If that lookup fails we can safely assume that the + * file does not exist at all. This is signified by DirLookupAbs() + * returning an empty string. + */ + if (DEBUG(DIR)) { + fprintf(debug_file, " Trying exact path matches...\n"); + } + + if (!hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp)) + != NULL)) { + if (file[0] == '\0') { + free(file); + return NULL; + } + return file; + } + + (void)Lst_Open(path); + while ((ln = Lst_Next(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + if (p == dotLast) + continue; + if ((file = DirLookupAbs(p, name, cp)) != NULL) { + Lst_Close(path); + if (file[0] == '\0') { + free(file); + return NULL; + } + return file; + } + } + Lst_Close(path); + + if (hasLastDot && cur && ((file = DirLookupAbs(cur, name, cp)) + != NULL)) { + if (file[0] == '\0') { + free(file); + return NULL; + } + return file; + } + } + + /* + * Didn't find it that way, either. Sigh. Phase 3. Add its directory + * onto the search path in any case, just in case, then look for the + * thing in the hash table. If we find it, grand. We return a new + * copy of the name. Otherwise we sadly return a NULL pointer. Sigh. + * Note that if the directory holding the file doesn't exist, this will + * do an extra search of the final directory on the path. Unless something + * weird happens, this search won't succeed and life will be groovy. + * + * Sigh. We cannot add the directory onto the search path because + * of this amusing case: + * $(INSTALLDIR)/$(FILE): $(FILE) + * + * $(FILE) exists in $(INSTALLDIR) but not in the current one. + * When searching for $(FILE), we will find it in $(INSTALLDIR) + * b/c we added it here. This is not good... + */ +#ifdef notdef + if (cp == traling_dot) { + cp = strrchr(name, '/'); + cp += 1; + } + cp[-1] = '\0'; + (void)Dir_AddDir(path, name); + cp[-1] = '/'; + + bigmisses += 1; + ln = Lst_Last(path); + if (ln == NULL) { + return NULL; + } else { + p = (Path *)Lst_Datum(ln); + } + + if (Hash_FindEntry(&p->files, cp) != NULL) { + return (bmake_strdup(name)); + } else { + return NULL; + } +#else /* !notdef */ + if (DEBUG(DIR)) { + fprintf(debug_file, " Looking for \"%s\" ...\n", name); + } + + bigmisses += 1; + if (cached_stat(name, &stb) == 0) { + return (bmake_strdup(name)); + } + + if (DEBUG(DIR)) { + fprintf(debug_file, " failed. Returning NULL\n"); + } + return NULL; +#endif /* notdef */ +} + + +/*- + *----------------------------------------------------------------------- + * Dir_FindHereOrAbove -- + * search for a path starting at a given directory and then working + * our way up towards the root. + * + * Input: + * here starting directory + * search_path the path we are looking for + * result the result of a successful search is placed here + * rlen the length of the result buffer + * (typically MAXPATHLEN + 1) + * + * Results: + * 0 on failure, 1 on success [in which case the found path is put + * in the result buffer]. + * + * Side Effects: + *----------------------------------------------------------------------- + */ +int +Dir_FindHereOrAbove(char *here, char *search_path, char *result, int rlen) { + + struct stat st; + char dirbase[MAXPATHLEN + 1], *db_end; + char try[MAXPATHLEN + 1], *try_end; + + /* copy out our starting point */ + snprintf(dirbase, sizeof(dirbase), "%s", here); + db_end = dirbase + strlen(dirbase); + + /* loop until we determine a result */ + while (1) { + + /* try and stat(2) it ... */ + snprintf(try, sizeof(try), "%s/%s", dirbase, search_path); + if (cached_stat(try, &st) != -1) { + /* + * success! if we found a file, chop off + * the filename so we return a directory. + */ + if ((st.st_mode & S_IFMT) != S_IFDIR) { + try_end = try + strlen(try); + while (try_end > try && *try_end != '/') + try_end--; + if (try_end > try) + *try_end = 0; /* chop! */ + } + + /* + * done! + */ + snprintf(result, rlen, "%s", try); + return(1); + } + + /* + * nope, we didn't find it. if we used up dirbase we've + * reached the root and failed. + */ + if (db_end == dirbase) + break; /* failed! */ + + /* + * truncate dirbase from the end to move up a dir + */ + while (db_end > dirbase && *db_end != '/') + db_end--; + *db_end = 0; /* chop! */ + + } /* while (1) */ + + /* + * we failed... + */ + return(0); +} + +/*- + *----------------------------------------------------------------------- + * Dir_MTime -- + * Find the modification time of the file described by gn along the + * search path dirSearchPath. + * + * Input: + * gn the file whose modification time is desired + * + * Results: + * The modification time or 0 if it doesn't exist + * + * Side Effects: + * The modification time is placed in the node's mtime slot. + * If the node didn't have a path entry before, and Dir_FindFile + * found one for it, the full name is placed in the path slot. + *----------------------------------------------------------------------- + */ +int +Dir_MTime(GNode *gn, Boolean recheck) +{ + char *fullName; /* the full pathname of name */ + struct stat stb; /* buffer for finding the mod time */ + + if (gn->type & OP_ARCHV) { + return Arch_MTime(gn); + } else if (gn->type & OP_PHONY) { + gn->mtime = 0; + return 0; + } else if (gn->path == NULL) { + if (gn->type & OP_NOPATH) + fullName = NULL; + else { + fullName = Dir_FindFile(gn->name, Suff_FindPath(gn)); + if (fullName == NULL && gn->flags & FROM_DEPEND && + !Lst_IsEmpty(gn->iParents)) { + char *cp; + + cp = strrchr(gn->name, '/'); + if (cp) { + /* + * This is an implied source, and it may have moved, + * see if we can find it via the current .PATH + */ + cp++; + + fullName = Dir_FindFile(cp, Suff_FindPath(gn)); + if (fullName) { + /* + * Put the found file in gn->path + * so that we give that to the compiler. + */ + gn->path = bmake_strdup(fullName); + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, + "%s: %s, %d: ignoring stale %s for %s, " + "found %s\n", progname, gn->fname, gn->lineno, + makeDependfile, gn->name, fullName); + } + } + } + if (DEBUG(DIR)) + fprintf(debug_file, "Found '%s' as '%s'\n", + gn->name, fullName ? fullName : "(not found)" ); + } + } else { + fullName = gn->path; + } + + if (fullName == NULL) { + fullName = bmake_strdup(gn->name); + } + + if (cached_stats(&mtimes, fullName, &stb, recheck ? CST_UPDATE : 0) < 0) { + if (gn->type & OP_MEMBER) { + if (fullName != gn->path) + free(fullName); + return Arch_MemMTime(gn); + } else { + stb.st_mtime = 0; + } + } + + if (fullName && gn->path == NULL) { + gn->path = fullName; + } + + gn->mtime = stb.st_mtime; + return (gn->mtime); +} + +/*- + *----------------------------------------------------------------------- + * Dir_AddDir -- + * Add the given name to the end of the given path. The order of + * the arguments is backwards so ParseDoDependency can do a + * Lst_ForEach of its list of paths... + * + * Input: + * path the path to which the directory should be + * added + * name the name of the directory to add + * + * Results: + * none + * + * Side Effects: + * A structure is added to the list and the directory is + * read and hashed. + *----------------------------------------------------------------------- + */ +Path * +Dir_AddDir(Lst path, const char *name) +{ + LstNode ln = NULL; /* node in case Path structure is found */ + Path *p = NULL; /* pointer to new Path structure */ + DIR *d; /* for reading directory */ + struct dirent *dp; /* entry in directory */ + + if (strcmp(name, ".DOTLAST") == 0) { + ln = Lst_Find(path, name, DirFindName); + if (ln != NULL) + return (Path *)Lst_Datum(ln); + else { + dotLast->refCount += 1; + (void)Lst_AtFront(path, dotLast); + } + } + + if (path) + ln = Lst_Find(openDirectories, name, DirFindName); + if (ln != NULL) { + p = (Path *)Lst_Datum(ln); + if (path && Lst_Member(path, p) == NULL) { + p->refCount += 1; + (void)Lst_AtEnd(path, p); + } + } else { + if (DEBUG(DIR)) { + fprintf(debug_file, "Caching %s ...", name); + } + + if ((d = opendir(name)) != NULL) { + p = bmake_malloc(sizeof(Path)); + p->name = bmake_strdup(name); + p->hits = 0; + p->refCount = 1; + Hash_InitTable(&p->files, -1); + + while ((dp = readdir(d)) != NULL) { +#if defined(sun) && defined(d_ino) /* d_ino is a sunos4 #define for d_fileno */ + /* + * The sun directory library doesn't check for a 0 inode + * (0-inode slots just take up space), so we have to do + * it ourselves. + */ + if (dp->d_fileno == 0) { + continue; + } +#endif /* sun && d_ino */ + (void)Hash_CreateEntry(&p->files, dp->d_name, NULL); + } + (void)closedir(d); + (void)Lst_AtEnd(openDirectories, p); + if (path != NULL) + (void)Lst_AtEnd(path, p); + } + if (DEBUG(DIR)) { + fprintf(debug_file, "done\n"); + } + } + return p; +} + +/*- + *----------------------------------------------------------------------- + * Dir_CopyDir -- + * Callback function for duplicating a search path via Lst_Duplicate. + * Ups the reference count for the directory. + * + * Results: + * Returns the Path it was given. + * + * Side Effects: + * The refCount of the path is incremented. + * + *----------------------------------------------------------------------- + */ +void * +Dir_CopyDir(void *p) +{ + ((Path *)p)->refCount += 1; + + return (p); +} + +/*- + *----------------------------------------------------------------------- + * Dir_MakeFlags -- + * Make a string by taking all the directories in the given search + * path and preceding them by the given flag. Used by the suffix + * module to create variables for compilers based on suffix search + * paths. + * + * Input: + * flag flag which should precede each directory + * path list of directories + * + * Results: + * The string mentioned above. Note that there is no space between + * the given flag and each directory. The empty string is returned if + * Things don't go well. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Dir_MakeFlags(const char *flag, Lst path) +{ + char *str; /* the string which will be returned */ + char *s1, *s2;/* the current directory preceded by 'flag' */ + LstNode ln; /* the node of the current directory */ + Path *p; /* the structure describing the current directory */ + + str = bmake_strdup(""); + + if (Lst_Open(path) == SUCCESS) { + while ((ln = Lst_Next(path)) != NULL) { + p = (Path *)Lst_Datum(ln); + s2 = str_concat(flag, p->name, 0); + str = str_concat(s1 = str, s2, STR_ADDSPACE); + free(s1); + free(s2); + } + Lst_Close(path); + } + + return (str); +} + +/*- + *----------------------------------------------------------------------- + * Dir_Destroy -- + * Nuke a directory descriptor, if possible. Callback procedure + * for the suffixes module when destroying a search path. + * + * Input: + * pp The directory descriptor to nuke + * + * Results: + * None. + * + * Side Effects: + * If no other path references this directory (refCount == 0), + * the Path and all its data are freed. + * + *----------------------------------------------------------------------- + */ +void +Dir_Destroy(void *pp) +{ + Path *p = (Path *)pp; + p->refCount -= 1; + + if (p->refCount == 0) { + LstNode ln; + + ln = Lst_Member(openDirectories, p); + (void)Lst_Remove(openDirectories, ln); + + Hash_DeleteTable(&p->files); + free(p->name); + free(p); + } +} + +/*- + *----------------------------------------------------------------------- + * Dir_ClearPath -- + * Clear out all elements of the given search path. This is different + * from destroying the list, notice. + * + * Input: + * path Path to clear + * + * Results: + * None. + * + * Side Effects: + * The path is set to the empty list. + * + *----------------------------------------------------------------------- + */ +void +Dir_ClearPath(Lst path) +{ + Path *p; + while (!Lst_IsEmpty(path)) { + p = (Path *)Lst_DeQueue(path); + Dir_Destroy(p); + } +} + + +/*- + *----------------------------------------------------------------------- + * Dir_Concat -- + * Concatenate two paths, adding the second to the end of the first. + * Makes sure to avoid duplicates. + * + * Input: + * path1 Dest + * path2 Source + * + * Results: + * None + * + * Side Effects: + * Reference counts for added dirs are upped. + * + *----------------------------------------------------------------------- + */ +void +Dir_Concat(Lst path1, Lst path2) +{ + LstNode ln; + Path *p; + + for (ln = Lst_First(path2); ln != NULL; ln = Lst_Succ(ln)) { + p = (Path *)Lst_Datum(ln); + if (Lst_Member(path1, p) == NULL) { + p->refCount += 1; + (void)Lst_AtEnd(path1, p); + } + } +} + +/********** DEBUG INFO **********/ +void +Dir_PrintDirectories(void) +{ + LstNode ln; + Path *p; + + fprintf(debug_file, "#*** Directory Cache:\n"); + fprintf(debug_file, "# Stats: %d hits %d misses %d near misses %d losers (%d%%)\n", + hits, misses, nearmisses, bigmisses, + (hits+bigmisses+nearmisses ? + hits * 100 / (hits + bigmisses + nearmisses) : 0)); + fprintf(debug_file, "# %-20s referenced\thits\n", "directory"); + if (Lst_Open(openDirectories) == SUCCESS) { + while ((ln = Lst_Next(openDirectories)) != NULL) { + p = (Path *)Lst_Datum(ln); + fprintf(debug_file, "# %-20s %10d\t%4d\n", p->name, p->refCount, p->hits); + } + Lst_Close(openDirectories); + } +} + +static int +DirPrintDir(void *p, void *dummy MAKE_ATTR_UNUSED) +{ + fprintf(debug_file, "%s ", ((Path *)p)->name); + return 0; +} + +void +Dir_PrintPath(Lst path) +{ + Lst_ForEach(path, DirPrintDir, NULL); +} diff --git a/usr.bin/make/dir.h b/usr.bin/make/dir.h new file mode 100644 index 0000000..52ab35e --- /dev/null +++ b/usr.bin/make/dir.h @@ -0,0 +1,108 @@ +/* $NetBSD: dir.h,v 1.18 2017/05/31 22:02:06 maya Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)dir.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)dir.h 8.1 (Berkeley) 6/6/93 + */ + +/* dir.h -- + */ + +#ifndef MAKE_DIR_H +#define MAKE_DIR_H + +typedef struct Path { + char *name; /* Name of directory */ + int refCount; /* Number of paths with this directory */ + int hits; /* the number of times a file in this + * directory has been found */ + Hash_Table files; /* Hash table of files in directory */ +} Path; + +void Dir_Init(const char *); +void Dir_InitCur(const char *); +void Dir_InitDot(void); +void Dir_End(void); +void Dir_SetPATH(void); +Boolean Dir_HasWildcards(char *); +void Dir_Expand(const char *, Lst, Lst); +char *Dir_FindFile(const char *, Lst); +int Dir_FindHereOrAbove(char *, char *, char *, int); +int Dir_MTime(GNode *, Boolean); +Path *Dir_AddDir(Lst, const char *); +char *Dir_MakeFlags(const char *, Lst); +void Dir_ClearPath(Lst); +void Dir_Concat(Lst, Lst); +void Dir_PrintDirectories(void); +void Dir_PrintPath(Lst); +void Dir_Destroy(void *); +void * Dir_CopyDir(void *); + +#endif /* MAKE_DIR_H */ diff --git a/usr.bin/make/for.c b/usr.bin/make/for.c new file mode 100644 index 0000000..fffedda --- /dev/null +++ b/usr.bin/make/for.c @@ -0,0 +1,496 @@ +/* $NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $ */ + +/* + * Copyright (c) 1992, The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)for.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: for.c,v 1.53 2017/04/16 21:04:44 riastradh Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * for.c -- + * Functions to handle loops in a makefile. + * + * Interface: + * For_Eval Evaluate the loop in the passed line. + * For_Run Run accumulated loop + * + */ + +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "buf.h" +#include "strlist.h" + +#define FOR_SUB_ESCAPE_CHAR 1 +#define FOR_SUB_ESCAPE_BRACE 2 +#define FOR_SUB_ESCAPE_PAREN 4 + +/* + * For statements are of the form: + * + * .for in + * ... + * .endfor + * + * The trick is to look for the matching end inside for for loop + * To do that, we count the current nesting level of the for loops. + * and the .endfor statements, accumulating all the statements between + * the initial .for loop and the matching .endfor; + * then we evaluate the for loop for each variable in the varlist. + * + * Note that any nested fors are just passed through; they get handled + * recursively in For_Eval when we're expanding the enclosing for in + * For_Run. + */ + +static int forLevel = 0; /* Nesting level */ + +/* + * State of a for loop. + */ +typedef struct _For { + Buffer buf; /* Body of loop */ + strlist_t vars; /* Iteration variables */ + strlist_t items; /* Substitution items */ + char *parse_buf; + int short_var; + int sub_next; +} For; + +static For *accumFor; /* Loop being accumulated */ + + + +static char * +make_str(const char *ptr, int len) +{ + char *new_ptr; + + new_ptr = bmake_malloc(len + 1); + memcpy(new_ptr, ptr, len); + new_ptr[len] = 0; + return new_ptr; +} + +static void +For_Free(For *arg) +{ + Buf_Destroy(&arg->buf, TRUE); + strlist_clean(&arg->vars); + strlist_clean(&arg->items); + free(arg->parse_buf); + + free(arg); +} + +/*- + *----------------------------------------------------------------------- + * For_Eval -- + * Evaluate the for loop in the passed line. The line + * looks like this: + * .for in + * + * Input: + * line Line to parse + * + * Results: + * 0: Not a .for statement, parse the line + * 1: We found a for loop + * -1: A .for statement with a bad syntax error, discard. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +int +For_Eval(char *line) +{ + For *new_for; + char *ptr = line, *sub; + int len; + int escapes; + unsigned char ch; + char **words, *word_buf; + int n, nwords; + + /* Skip the '.' and any following whitespace */ + for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) + continue; + + /* + * If we are not in a for loop quickly determine if the statement is + * a for. + */ + if (ptr[0] != 'f' || ptr[1] != 'o' || ptr[2] != 'r' || + !isspace((unsigned char) ptr[3])) { + if (ptr[0] == 'e' && strncmp(ptr+1, "ndfor", 5) == 0) { + Parse_Error(PARSE_FATAL, "for-less endfor"); + return -1; + } + return 0; + } + ptr += 3; + + /* + * we found a for loop, and now we are going to parse it. + */ + + new_for = bmake_malloc(sizeof *new_for); + memset(new_for, 0, sizeof *new_for); + + /* Grab the variables. Terminate on "in". */ + for (;; ptr += len) { + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + if (*ptr == '\0') { + Parse_Error(PARSE_FATAL, "missing `in' in for"); + For_Free(new_for); + return -1; + } + for (len = 1; ptr[len] && !isspace((unsigned char)ptr[len]); len++) + continue; + if (len == 2 && ptr[0] == 'i' && ptr[1] == 'n') { + ptr += 2; + break; + } + if (len == 1) + new_for->short_var = 1; + strlist_add_str(&new_for->vars, make_str(ptr, len), len); + } + + if (strlist_num(&new_for->vars) == 0) { + Parse_Error(PARSE_FATAL, "no iteration variables in for"); + For_Free(new_for); + return -1; + } + + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + + /* + * Make a list with the remaining words + * The values are substituted as ${:U...} so we must \ escape + * characters that break that syntax. + * Variables are fully expanded - so it is safe for escape $. + * We can't do the escapes here - because we don't know whether + * we are substuting into ${...} or $(...). + */ + sub = Var_Subst(NULL, ptr, VAR_GLOBAL, VARF_WANTRES); + + /* + * Split into words allowing for quoted strings. + */ + words = brk_string(sub, &nwords, FALSE, &word_buf); + + free(sub); + + if (words != NULL) { + for (n = 0; n < nwords; n++) { + ptr = words[n]; + if (!*ptr) + continue; + escapes = 0; + while ((ch = *ptr++)) { + switch(ch) { + case ':': + case '$': + case '\\': + escapes |= FOR_SUB_ESCAPE_CHAR; + break; + case ')': + escapes |= FOR_SUB_ESCAPE_PAREN; + break; + case /*{*/ '}': + escapes |= FOR_SUB_ESCAPE_BRACE; + break; + } + } + /* + * We have to dup words[n] to maintain the semantics of + * strlist. + */ + strlist_add_str(&new_for->items, bmake_strdup(words[n]), escapes); + } + + free(words); + free(word_buf); + + if ((len = strlist_num(&new_for->items)) > 0 && + len % (n = strlist_num(&new_for->vars))) { + Parse_Error(PARSE_FATAL, + "Wrong number of words (%d) in .for substitution list" + " with %d vars", len, n); + /* + * Return 'success' so that the body of the .for loop is + * accumulated. + * Remove all items so that the loop doesn't iterate. + */ + strlist_clean(&new_for->items); + } + } + + Buf_Init(&new_for->buf, 0); + accumFor = new_for; + forLevel = 1; + return 1; +} + +/* + * Add another line to a .for loop. + * Returns 0 when the matching .endfor is reached. + */ + +int +For_Accum(char *line) +{ + char *ptr = line; + + if (*ptr == '.') { + + for (ptr++; *ptr && isspace((unsigned char) *ptr); ptr++) + continue; + + if (strncmp(ptr, "endfor", 6) == 0 && + (isspace((unsigned char) ptr[6]) || !ptr[6])) { + if (DEBUG(FOR)) + (void)fprintf(debug_file, "For: end for %d\n", forLevel); + if (--forLevel <= 0) + return 0; + } else if (strncmp(ptr, "for", 3) == 0 && + isspace((unsigned char) ptr[3])) { + forLevel++; + if (DEBUG(FOR)) + (void)fprintf(debug_file, "For: new loop %d\n", forLevel); + } + } + + Buf_AddBytes(&accumFor->buf, strlen(line), line); + Buf_AddByte(&accumFor->buf, '\n'); + return 1; +} + + +/*- + *----------------------------------------------------------------------- + * For_Run -- + * Run the for loop, imitating the actions of an include file + * + * Results: + * None. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ + +static int +for_var_len(const char *var) +{ + char ch, var_start, var_end; + int depth; + int len; + + var_start = *var; + if (var_start == 0) + /* just escape the $ */ + return 0; + + if (var_start == '(') + var_end = ')'; + else if (var_start == '{') + var_end = '}'; + else + /* Single char variable */ + return 1; + + depth = 1; + for (len = 1; (ch = var[len++]) != 0;) { + if (ch == var_start) + depth++; + else if (ch == var_end && --depth == 0) + return len; + } + + /* Variable end not found, escape the $ */ + return 0; +} + +static void +for_substitute(Buffer *cmds, strlist_t *items, unsigned int item_no, char ech) +{ + const char *item = strlist_str(items, item_no); + int len; + char ch; + + /* If there were no escapes, or the only escape is the other variable + * terminator, then just substitute the full string */ + if (!(strlist_info(items, item_no) & + (ech == ')' ? ~FOR_SUB_ESCAPE_BRACE : ~FOR_SUB_ESCAPE_PAREN))) { + Buf_AddBytes(cmds, strlen(item), item); + return; + } + + /* Escape ':', '$', '\\' and 'ech' - removed by :U processing */ + while ((ch = *item++) != 0) { + if (ch == '$') { + len = for_var_len(item); + if (len != 0) { + Buf_AddBytes(cmds, len + 1, item - 1); + item += len; + continue; + } + Buf_AddByte(cmds, '\\'); + } else if (ch == ':' || ch == '\\' || ch == ech) + Buf_AddByte(cmds, '\\'); + Buf_AddByte(cmds, ch); + } +} + +static char * +For_Iterate(void *v_arg, size_t *ret_len) +{ + For *arg = v_arg; + int i, len; + char *var; + char *cp; + char *cmd_cp; + char *body_end; + char ch; + Buffer cmds; + + if (arg->sub_next + strlist_num(&arg->vars) > strlist_num(&arg->items)) { + /* No more iterations */ + For_Free(arg); + return NULL; + } + + free(arg->parse_buf); + arg->parse_buf = NULL; + + /* + * Scan the for loop body and replace references to the loop variables + * with variable references that expand to the required text. + * Using variable expansions ensures that the .for loop can't generate + * syntax, and that the later parsing will still see a variable. + * We assume that the null variable will never be defined. + * + * The detection of substitions of the loop control variable is naive. + * Many of the modifiers use \ to escape $ (not $) so it is possible + * to contrive a makefile where an unwanted substitution happens. + */ + + cmd_cp = Buf_GetAll(&arg->buf, &len); + body_end = cmd_cp + len; + Buf_Init(&cmds, len + 256); + for (cp = cmd_cp; (cp = strchr(cp, '$')) != NULL;) { + char ech; + ch = *++cp; + if ((ch == '(' && (ech = ')', 1)) || (ch == '{' && (ech = '}', 1))) { + cp++; + /* Check variable name against the .for loop variables */ + STRLIST_FOREACH(var, &arg->vars, i) { + len = strlist_info(&arg->vars, i); + if (memcmp(cp, var, len) != 0) + continue; + if (cp[len] != ':' && cp[len] != ech && cp[len] != '\\') + continue; + /* Found a variable match. Replace with :U */ + Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); + Buf_AddBytes(&cmds, 2, ":U"); + cp += len; + cmd_cp = cp; + for_substitute(&cmds, &arg->items, arg->sub_next + i, ech); + break; + } + continue; + } + if (ch == 0) + break; + /* Probably a single character name, ignore $$ and stupid ones. {*/ + if (!arg->short_var || strchr("}):$", ch) != NULL) { + cp++; + continue; + } + STRLIST_FOREACH(var, &arg->vars, i) { + if (var[0] != ch || var[1] != 0) + continue; + /* Found a variable match. Replace with ${:U} */ + Buf_AddBytes(&cmds, cp - cmd_cp, cmd_cp); + Buf_AddBytes(&cmds, 3, "{:U"); + cmd_cp = ++cp; + for_substitute(&cmds, &arg->items, arg->sub_next + i, /*{*/ '}'); + Buf_AddBytes(&cmds, 1, "}"); + break; + } + } + Buf_AddBytes(&cmds, body_end - cmd_cp, cmd_cp); + + cp = Buf_Destroy(&cmds, FALSE); + if (DEBUG(FOR)) + (void)fprintf(debug_file, "For: loop body:\n%s", cp); + + arg->sub_next += strlist_num(&arg->vars); + + arg->parse_buf = cp; + *ret_len = strlen(cp); + return cp; +} + +void +For_Run(int lineno) +{ + For *arg; + + arg = accumFor; + accumFor = NULL; + + if (strlist_num(&arg->items) == 0) { + /* Nothing to expand - possibly due to an earlier syntax error. */ + For_Free(arg); + return; + } + + Parse_SetInput(NULL, lineno, -1, For_Iterate, arg); +} diff --git a/usr.bin/make/hash.c b/usr.bin/make/hash.c new file mode 100644 index 0000000..ed23644 --- /dev/null +++ b/usr.bin/make/hash.c @@ -0,0 +1,466 @@ +/* $NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)hash.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: hash.c,v 1.20 2013/11/14 00:27:05 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/* hash.c -- + * + * This module contains routines to manipulate a hash table. + * See hash.h for a definition of the structure of the hash + * table. Hash tables grow automatically as the amount of + * information increases. + */ +#include "sprite.h" +#include "make.h" +#include "hash.h" + +/* + * Forward references to local procedures that are used before they're + * defined: + */ + +static void RebuildTable(Hash_Table *); + +/* + * The following defines the ratio of # entries to # buckets + * at which we rebuild the table to make it larger. + */ + +#define rebuildLimit 3 + +/* + *--------------------------------------------------------- + * + * Hash_InitTable -- + * + * This routine just sets up the hash table. + * + * Input: + * t Structure to to hold table. + * numBuckets How many buckets to create for starters. This + * number is rounded up to a power of two. If + * <= 0, a reasonable default is chosen. The + * table will grow in size later as needed. + * + * Results: + * None. + * + * Side Effects: + * Memory is allocated for the initial bucket area. + * + *--------------------------------------------------------- + */ + +void +Hash_InitTable(Hash_Table *t, int numBuckets) +{ + int i; + struct Hash_Entry **hp; + + /* + * Round up the size to a power of two. + */ + if (numBuckets <= 0) + i = 16; + else { + for (i = 2; i < numBuckets; i <<= 1) + continue; + } + t->numEntries = 0; + t->size = i; + t->mask = i - 1; + t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i); + while (--i >= 0) + *hp++ = NULL; +} + +/* + *--------------------------------------------------------- + * + * Hash_DeleteTable -- + * + * This routine removes everything from a hash table + * and frees up the memory space it occupied (except for + * the space in the Hash_Table structure). + * + * Results: + * None. + * + * Side Effects: + * Lots of memory is freed up. + * + *--------------------------------------------------------- + */ + +void +Hash_DeleteTable(Hash_Table *t) +{ + struct Hash_Entry **hp, *h, *nexth = NULL; + int i; + + for (hp = t->bucketPtr, i = t->size; --i >= 0;) { + for (h = *hp++; h != NULL; h = nexth) { + nexth = h->next; + free(h); + } + } + free(t->bucketPtr); + + /* + * Set up the hash table to cause memory faults on any future access + * attempts until re-initialization. + */ + t->bucketPtr = NULL; +} + +/* + *--------------------------------------------------------- + * + * Hash_FindEntry -- + * + * Searches a hash table for an entry corresponding to key. + * + * Input: + * t Hash table to search. + * key A hash key. + * + * Results: + * The return value is a pointer to the entry for key, + * if key was present in the table. If key was not + * present, NULL is returned. + * + * Side Effects: + * None. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_FindEntry(Hash_Table *t, const char *key) +{ + Hash_Entry *e; + unsigned h; + const char *p; + + if (t == NULL || t->bucketPtr == NULL) { + return NULL; + } + for (h = 0, p = key; *p;) + h = (h << 5) - h + *p++; + p = key; + for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) + if (e->namehash == h && strcmp(e->name, p) == 0) + return (e); + return NULL; +} + +/* + *--------------------------------------------------------- + * + * Hash_CreateEntry -- + * + * Searches a hash table for an entry corresponding to + * key. If no entry is found, then one is created. + * + * Input: + * t Hash table to search. + * key A hash key. + * newPtr Filled in with TRUE if new entry created, + * FALSE otherwise. + * + * Results: + * The return value is a pointer to the entry. If *newPtr + * isn't NULL, then *newPtr is filled in with TRUE if a + * new entry was created, and FALSE if an entry already existed + * with the given key. + * + * Side Effects: + * Memory may be allocated, and the hash buckets may be modified. + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_CreateEntry(Hash_Table *t, const char *key, Boolean *newPtr) +{ + Hash_Entry *e; + unsigned h; + const char *p; + int keylen; + struct Hash_Entry **hp; + + /* + * Hash the key. As a side effect, save the length (strlen) of the + * key in case we need to create the entry. + */ + for (h = 0, p = key; *p;) + h = (h << 5) - h + *p++; + keylen = p - key; + p = key; + for (e = t->bucketPtr[h & t->mask]; e != NULL; e = e->next) { + if (e->namehash == h && strcmp(e->name, p) == 0) { + if (newPtr != NULL) + *newPtr = FALSE; + return (e); + } + } + + /* + * The desired entry isn't there. Before allocating a new entry, + * expand the table if necessary (and this changes the resulting + * bucket chain). + */ + if (t->numEntries >= rebuildLimit * t->size) + RebuildTable(t); + e = bmake_malloc(sizeof(*e) + keylen); + hp = &t->bucketPtr[h & t->mask]; + e->next = *hp; + *hp = e; + Hash_SetValue(e, NULL); + e->namehash = h; + (void)strcpy(e->name, p); + t->numEntries++; + + if (newPtr != NULL) + *newPtr = TRUE; + return (e); +} + +/* + *--------------------------------------------------------- + * + * Hash_DeleteEntry -- + * + * Delete the given hash table entry and free memory associated with + * it. + * + * Results: + * None. + * + * Side Effects: + * Hash chain that entry lives in is modified and memory is freed. + * + *--------------------------------------------------------- + */ + +void +Hash_DeleteEntry(Hash_Table *t, Hash_Entry *e) +{ + Hash_Entry **hp, *p; + + if (e == NULL) + return; + for (hp = &t->bucketPtr[e->namehash & t->mask]; + (p = *hp) != NULL; hp = &p->next) { + if (p == e) { + *hp = p->next; + free(p); + t->numEntries--; + return; + } + } + (void)write(2, "bad call to Hash_DeleteEntry\n", 29); + abort(); +} + +/* + *--------------------------------------------------------- + * + * Hash_EnumFirst -- + * This procedure sets things up for a complete search + * of all entries recorded in the hash table. + * + * Input: + * t Table to be searched. + * searchPtr Area in which to keep state about search. + * + * Results: + * The return value is the address of the first entry in + * the hash table, or NULL if the table is empty. + * + * Side Effects: + * The information in searchPtr is initialized so that successive + * calls to Hash_Next will return successive HashEntry's + * from the table. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_EnumFirst(Hash_Table *t, Hash_Search *searchPtr) +{ + searchPtr->tablePtr = t; + searchPtr->nextIndex = 0; + searchPtr->hashEntryPtr = NULL; + return Hash_EnumNext(searchPtr); +} + +/* + *--------------------------------------------------------- + * + * Hash_EnumNext -- + * This procedure returns successive entries in the hash table. + * + * Input: + * searchPtr Area used to keep state about search. + * + * Results: + * The return value is a pointer to the next HashEntry + * in the table, or NULL when the end of the table is + * reached. + * + * Side Effects: + * The information in searchPtr is modified to advance to the + * next entry. + * + *--------------------------------------------------------- + */ + +Hash_Entry * +Hash_EnumNext(Hash_Search *searchPtr) +{ + Hash_Entry *e; + Hash_Table *t = searchPtr->tablePtr; + + /* + * The hashEntryPtr field points to the most recently returned + * entry, or is nil if we are starting up. If not nil, we have + * to start at the next one in the chain. + */ + e = searchPtr->hashEntryPtr; + if (e != NULL) + e = e->next; + /* + * If the chain ran out, or if we are starting up, we need to + * find the next nonempty chain. + */ + while (e == NULL) { + if (searchPtr->nextIndex >= t->size) + return NULL; + e = t->bucketPtr[searchPtr->nextIndex++]; + } + searchPtr->hashEntryPtr = e; + return (e); +} + +/* + *--------------------------------------------------------- + * + * RebuildTable -- + * This local routine makes a new hash table that + * is larger than the old one. + * + * Results: + * None. + * + * Side Effects: + * The entire hash table is moved, so any bucket numbers + * from the old table are invalid. + * + *--------------------------------------------------------- + */ + +static void +RebuildTable(Hash_Table *t) +{ + Hash_Entry *e, *next = NULL, **hp, **xp; + int i, mask; + Hash_Entry **oldhp; + int oldsize; + + oldhp = t->bucketPtr; + oldsize = i = t->size; + i <<= 1; + t->size = i; + t->mask = mask = i - 1; + t->bucketPtr = hp = bmake_malloc(sizeof(*hp) * i); + while (--i >= 0) + *hp++ = NULL; + for (hp = oldhp, i = oldsize; --i >= 0;) { + for (e = *hp++; e != NULL; e = next) { + next = e->next; + xp = &t->bucketPtr[e->namehash & mask]; + e->next = *xp; + *xp = e; + } + } + free(oldhp); +} diff --git a/usr.bin/make/hash.h b/usr.bin/make/hash.h new file mode 100644 index 0000000..8ab6ffd --- /dev/null +++ b/usr.bin/make/hash.h @@ -0,0 +1,149 @@ +/* $NetBSD: hash.h,v 1.12 2017/05/31 21:07:03 maya Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)hash.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)hash.h 8.1 (Berkeley) 6/6/93 + */ + +/* hash.h -- + * + * This file contains definitions used by the hash module, + * which maintains hash tables. + */ + +#ifndef _HASH_H +#define _HASH_H + +/* + * The following defines one entry in the hash table. + */ + +typedef struct Hash_Entry { + struct Hash_Entry *next; /* Used to link together all the + * entries associated with the same + * bucket. */ + void *clientPtr; /* Arbitrary pointer */ + unsigned namehash; /* hash value of key */ + char name[1]; /* key string */ +} Hash_Entry; + +typedef struct Hash_Table { + struct Hash_Entry **bucketPtr;/* Pointers to Hash_Entry, one + * for each bucket in the table. */ + int size; /* Actual size of array. */ + int numEntries; /* Number of entries in the table. */ + int mask; /* Used to select bits for hashing. */ +} Hash_Table; + +/* + * The following structure is used by the searching routines + * to record where we are in the search. + */ + +typedef struct Hash_Search { + Hash_Table *tablePtr; /* Table being searched. */ + int nextIndex; /* Next bucket to check (after current). */ + Hash_Entry *hashEntryPtr; /* Next entry to check in current bucket. */ +} Hash_Search; + +/* + * Macros. + */ + +/* + * void * Hash_GetValue(h) + * Hash_Entry *h; + */ + +#define Hash_GetValue(h) ((h)->clientPtr) + +/* + * Hash_SetValue(h, val); + * Hash_Entry *h; + * char *val; + */ + +#define Hash_SetValue(h, val) ((h)->clientPtr = (val)) + +/* + * Hash_Size(n) returns the number of words in an object of n bytes + */ + +#define Hash_Size(n) (((n) + sizeof (int) - 1) / sizeof (int)) + +void Hash_InitTable(Hash_Table *, int); +void Hash_DeleteTable(Hash_Table *); +Hash_Entry *Hash_FindEntry(Hash_Table *, const char *); +Hash_Entry *Hash_CreateEntry(Hash_Table *, const char *, Boolean *); +void Hash_DeleteEntry(Hash_Table *, Hash_Entry *); +Hash_Entry *Hash_EnumFirst(Hash_Table *, Hash_Search *); +Hash_Entry *Hash_EnumNext(Hash_Search *); + +#endif /* _HASH_H */ diff --git a/usr.bin/make/job.c b/usr.bin/make/job.c new file mode 100644 index 0000000..48ec284 --- /dev/null +++ b/usr.bin/make/job.c @@ -0,0 +1,3070 @@ +/* $NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)job.c 8.2 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: job.c,v 1.195 2018/05/13 22:13:28 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * job.c -- + * handle the creation etc. of our child processes. + * + * Interface: + * Job_Make Start the creation of the given target. + * + * Job_CatchChildren Check for and handle the termination of any + * children. This must be called reasonably + * frequently to keep the whole make going at + * a decent clip, since job table entries aren't + * removed until their process is caught this way. + * + * Job_CatchOutput Print any output our children have produced. + * Should also be called fairly frequently to + * keep the user informed of what's going on. + * If no output is waiting, it will block for + * a time given by the SEL_* constants, below, + * or until output is ready. + * + * Job_Init Called to initialize this module. in addition, + * any commands attached to the .BEGIN target + * are executed before this function returns. + * Hence, the makefile must have been parsed + * before this function is called. + * + * Job_End Cleanup any memory used. + * + * Job_ParseShell Given the line following a .SHELL target, parse + * the line as a shell specification. Returns + * FAILURE if the spec was incorrect. + * + * Job_Finish Perform any final processing which needs doing. + * This includes the execution of any commands + * which have been/were attached to the .END + * target. It should only be called when the + * job table is empty. + * + * Job_AbortAll Abort all currently running jobs. It doesn't + * handle output or do anything for the jobs, + * just kills them. It should only be called in + * an emergency, as it were. + * + * Job_CheckCommands Verify that the commands for a target are + * ok. Provide them if necessary and possible. + * + * Job_Touch Update a target without really updating it. + * + * Job_Wait Wait for all currently-running jobs to finish. + */ + +#include +#include +#include +#include +#include + +#include +#include +#ifndef USE_SELECT +#include +#endif +#include +#include +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" +#include "pathnames.h" +#include "trace.h" +# define STATIC static + +/* + * error handling variables + */ +static int errors = 0; /* number of errors reported */ +static int aborting = 0; /* why is the make aborting? */ +#define ABORT_ERROR 1 /* Because of an error */ +#define ABORT_INTERRUPT 2 /* Because it was interrupted */ +#define ABORT_WAIT 3 /* Waiting for jobs to finish */ +#define JOB_TOKENS "+EI+" /* Token to requeue for each abort state */ + +/* + * this tracks the number of tokens currently "out" to build jobs. + */ +int jobTokensRunning = 0; +int not_parallel = 0; /* set if .NOT_PARALLEL */ + +/* + * XXX: Avoid SunOS bug... FILENO() is fp->_file, and file + * is a char! So when we go above 127 we turn negative! + */ +#define FILENO(a) ((unsigned) fileno(a)) + +/* + * post-make command processing. The node postCommands is really just the + * .END target but we keep it around to avoid having to search for it + * all the time. + */ +static GNode *postCommands = NULL; + /* node containing commands to execute when + * everything else is done */ +static int numCommands; /* The number of commands actually printed + * for a target. Should this number be + * 0, no shell will be executed. */ + +/* + * Return values from JobStart. + */ +#define JOB_RUNNING 0 /* Job is running */ +#define JOB_ERROR 1 /* Error in starting the job */ +#define JOB_FINISHED 2 /* The job is already finished */ + +/* + * Descriptions for various shells. + * + * The build environment may set DEFSHELL_INDEX to one of + * DEFSHELL_INDEX_SH, DEFSHELL_INDEX_KSH, or DEFSHELL_INDEX_CSH, to + * select one of the prefedined shells as the default shell. + * + * Alternatively, the build environment may set DEFSHELL_CUSTOM to the + * name or the full path of a sh-compatible shell, which will be used as + * the default shell. + * + * ".SHELL" lines in Makefiles can choose the default shell from the + # set defined here, or add additional shells. + */ + +#ifdef DEFSHELL_CUSTOM +#define DEFSHELL_INDEX_CUSTOM 0 +#define DEFSHELL_INDEX_SH 1 +#define DEFSHELL_INDEX_KSH 2 +#define DEFSHELL_INDEX_CSH 3 +#else /* !DEFSHELL_CUSTOM */ +#define DEFSHELL_INDEX_SH 0 +#define DEFSHELL_INDEX_KSH 1 +#define DEFSHELL_INDEX_CSH 2 +#endif /* !DEFSHELL_CUSTOM */ + +#ifndef DEFSHELL_INDEX +#define DEFSHELL_INDEX 0 /* DEFSHELL_INDEX_CUSTOM or DEFSHELL_INDEX_SH */ +#endif /* !DEFSHELL_INDEX */ + +static Shell shells[] = { +#ifdef DEFSHELL_CUSTOM + /* + * An sh-compatible shell with a non-standard name. + * + * Keep this in sync with the "sh" description below, but avoid + * non-portable features that might not be supplied by all + * sh-compatible shells. + */ +{ + DEFSHELL_CUSTOM, + FALSE, "", "", "", 0, + FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', + "", + "", +}, +#endif /* DEFSHELL_CUSTOM */ + /* + * SH description. Echo control is also possible and, under + * sun UNIX anyway, one can even control error checking. + */ +{ + "sh", + FALSE, "", "", "", 0, + FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', +#if defined(MAKE_NATIVE) && defined(__NetBSD__) + "q", +#else + "", +#endif + "", +}, + /* + * KSH description. + */ +{ + "ksh", + TRUE, "set +v", "set -v", "set +v", 6, + FALSE, "echo \"%s\"\n", "%s\n", "{ %s \n} || exit $?\n", "'\n'", '#', + "v", + "", +}, + /* + * CSH description. The csh can do echo control by playing + * with the setting of the 'echo' shell variable. Sadly, + * however, it is unable to do error control nicely. + */ +{ + "csh", + TRUE, "unset verbose", "set verbose", "unset verbose", 10, + FALSE, "echo \"%s\"\n", "csh -c \"%s || exit 0\"\n", "", "'\\\n'", '#', + "v", "e", +}, + /* + * UNKNOWN. + */ +{ + NULL, + FALSE, NULL, NULL, NULL, 0, + FALSE, NULL, NULL, NULL, NULL, 0, + NULL, NULL, +} +}; +static Shell *commandShell = &shells[DEFSHELL_INDEX]; /* this is the shell to + * which we pass all + * commands in the Makefile. + * It is set by the + * Job_ParseShell function */ +const char *shellPath = NULL, /* full pathname of + * executable image */ + *shellName = NULL; /* last component of shell */ +char *shellErrFlag = NULL; +static const char *shellArgv = NULL; /* Custom shell args */ + + +STATIC Job *job_table; /* The structures that describe them */ +STATIC Job *job_table_end; /* job_table + maxJobs */ +static int wantToken; /* we want a token */ +static int lurking_children = 0; +static int make_suspended = 0; /* non-zero if we've seen a SIGTSTP (etc) */ + +/* + * Set of descriptors of pipes connected to + * the output channels of children + */ +static struct pollfd *fds = NULL; +static Job **jobfds = NULL; +static int nfds = 0; +static void watchfd(Job *); +static void clearfd(Job *); +static int readyfd(Job *); + +STATIC GNode *lastNode; /* The node for which output was most recently + * produced. */ +static char *targPrefix = NULL; /* What we print at the start of TARG_FMT */ +static Job tokenWaitJob; /* token wait pseudo-job */ + +static Job childExitJob; /* child exit pseudo-job */ +#define CHILD_EXIT "." +#define DO_JOB_RESUME "R" + +#define TARG_FMT "%s %s ---\n" /* Default format */ +#define MESSAGE(fp, gn) \ + if (maxJobs != 1 && targPrefix && *targPrefix) \ + (void)fprintf(fp, TARG_FMT, targPrefix, gn->name) + +static sigset_t caught_signals; /* Set of signals we handle */ + +static void JobChildSig(int); +static void JobContinueSig(int); +static Job *JobFindPid(int, int, Boolean); +static int JobPrintCommand(void *, void *); +static int JobSaveCommand(void *, void *); +static void JobClose(Job *); +static void JobExec(Job *, char **); +static void JobMakeArgv(Job *, char **); +static int JobStart(GNode *, int); +static char *JobOutput(Job *, char *, char *, int); +static void JobDoOutput(Job *, Boolean); +static Shell *JobMatchShell(const char *); +static void JobInterrupt(int, int) MAKE_ATTR_DEAD; +static void JobRestartJobs(void); +static void JobTokenAdd(void); +static void JobSigLock(sigset_t *); +static void JobSigUnlock(sigset_t *); +static void JobSigReset(void); + +const char *malloc_options="A"; + +static void +job_table_dump(const char *where) +{ + Job *job; + + fprintf(debug_file, "job table @ %s\n", where); + for (job = job_table; job < job_table_end; job++) { + fprintf(debug_file, "job %d, status %d, flags %d, pid %d\n", + (int)(job - job_table), job->job_state, job->flags, job->pid); + } +} + +/* + * Delete the target of a failed, interrupted, or otherwise + * unsuccessful job unless inhibited by .PRECIOUS. + */ +static void +JobDeleteTarget(GNode *gn) +{ + if ((gn->type & (OP_JOIN|OP_PHONY)) == 0 && !Targ_Precious(gn)) { + char *file = (gn->path == NULL ? gn->name : gn->path); + if (!noExecute && eunlink(file) != -1) { + Error("*** %s removed", file); + } + } +} + +/* + * JobSigLock/JobSigUnlock + * + * Signal lock routines to get exclusive access. Currently used to + * protect `jobs' and `stoppedJobs' list manipulations. + */ +static void JobSigLock(sigset_t *omaskp) +{ + if (sigprocmask(SIG_BLOCK, &caught_signals, omaskp) != 0) { + Punt("JobSigLock: sigprocmask: %s", strerror(errno)); + sigemptyset(omaskp); + } +} + +static void JobSigUnlock(sigset_t *omaskp) +{ + (void)sigprocmask(SIG_SETMASK, omaskp, NULL); +} + +static void +JobCreatePipe(Job *job, int minfd) +{ + int i, fd, flags; + + if (pipe(job->jobPipe) == -1) + Punt("Cannot create pipe: %s", strerror(errno)); + + for (i = 0; i < 2; i++) { + /* Avoid using low numbered fds */ + fd = fcntl(job->jobPipe[i], F_DUPFD, minfd); + if (fd != -1) { + close(job->jobPipe[i]); + job->jobPipe[i] = fd; + } + } + + /* Set close-on-exec flag for both */ + if (fcntl(job->jobPipe[0], F_SETFD, FD_CLOEXEC) == -1) + Punt("Cannot set close-on-exec: %s", strerror(errno)); + if (fcntl(job->jobPipe[1], F_SETFD, FD_CLOEXEC) == -1) + Punt("Cannot set close-on-exec: %s", strerror(errno)); + + /* + * We mark the input side of the pipe non-blocking; we poll(2) the + * pipe when we're waiting for a job token, but we might lose the + * race for the token when a new one becomes available, so the read + * from the pipe should not block. + */ + flags = fcntl(job->jobPipe[0], F_GETFL, 0); + if (flags == -1) + Punt("Cannot get flags: %s", strerror(errno)); + flags |= O_NONBLOCK; + if (fcntl(job->jobPipe[0], F_SETFL, flags) == -1) + Punt("Cannot set flags: %s", strerror(errno)); +} + +/*- + *----------------------------------------------------------------------- + * JobCondPassSig -- + * Pass a signal to a job + * + * Input: + * signop Signal to send it + * + * Side Effects: + * None, except the job may bite it. + * + *----------------------------------------------------------------------- + */ +static void +JobCondPassSig(int signo) +{ + Job *job; + + if (DEBUG(JOB)) { + (void)fprintf(debug_file, "JobCondPassSig(%d) called.\n", signo); + } + + for (job = job_table; job < job_table_end; job++) { + if (job->job_state != JOB_ST_RUNNING) + continue; + if (DEBUG(JOB)) { + (void)fprintf(debug_file, + "JobCondPassSig passing signal %d to child %d.\n", + signo, job->pid); + } + KILLPG(job->pid, signo); + } +} + +/*- + *----------------------------------------------------------------------- + * JobChldSig -- + * SIGCHLD handler. + * + * Input: + * signo The signal number we've received + * + * Results: + * None. + * + * Side Effects: + * Sends a token on the child exit pipe to wake us up from + * select()/poll(). + * + *----------------------------------------------------------------------- + */ +static void +JobChildSig(int signo MAKE_ATTR_UNUSED) +{ + while (write(childExitJob.outPipe, CHILD_EXIT, 1) == -1 && errno == EAGAIN) + continue; +} + + +/*- + *----------------------------------------------------------------------- + * JobContinueSig -- + * Resume all stopped jobs. + * + * Input: + * signo The signal number we've received + * + * Results: + * None. + * + * Side Effects: + * Jobs start running again. + * + *----------------------------------------------------------------------- + */ +static void +JobContinueSig(int signo MAKE_ATTR_UNUSED) +{ + /* + * Defer sending to SIGCONT to our stopped children until we return + * from the signal handler. + */ + while (write(childExitJob.outPipe, DO_JOB_RESUME, 1) == -1 && + errno == EAGAIN) + continue; +} + +/*- + *----------------------------------------------------------------------- + * JobPassSig -- + * Pass a signal on to all jobs, then resend to ourselves. + * + * Input: + * signo The signal number we've received + * + * Results: + * None. + * + * Side Effects: + * We die by the same signal. + * + *----------------------------------------------------------------------- + */ +MAKE_ATTR_DEAD static void +JobPassSig_int(int signo) +{ + /* Run .INTERRUPT target then exit */ + JobInterrupt(TRUE, signo); +} + +MAKE_ATTR_DEAD static void +JobPassSig_term(int signo) +{ + /* Dont run .INTERRUPT target then exit */ + JobInterrupt(FALSE, signo); +} + +static void +JobPassSig_suspend(int signo) +{ + sigset_t nmask, omask; + struct sigaction act; + + /* Suppress job started/continued messages */ + make_suspended = 1; + + /* Pass the signal onto every job */ + JobCondPassSig(signo); + + /* + * Send ourselves the signal now we've given the message to everyone else. + * Note we block everything else possible while we're getting the signal. + * This ensures that all our jobs get continued when we wake up before + * we take any other signal. + */ + sigfillset(&nmask); + sigdelset(&nmask, signo); + (void)sigprocmask(SIG_SETMASK, &nmask, &omask); + + act.sa_handler = SIG_DFL; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + (void)sigaction(signo, &act, NULL); + + if (DEBUG(JOB)) { + (void)fprintf(debug_file, + "JobPassSig passing signal %d to self.\n", signo); + } + + (void)kill(getpid(), signo); + + /* + * We've been continued. + * + * A whole host of signals continue to happen! + * SIGCHLD for any processes that actually suspended themselves. + * SIGCHLD for any processes that exited while we were alseep. + * The SIGCONT that actually caused us to wakeup. + * + * Since we defer passing the SIGCONT on to our children until + * the main processing loop, we can be sure that all the SIGCHLD + * events will have happened by then - and that the waitpid() will + * collect the child 'suspended' events. + * For correct sequencing we just need to ensure we process the + * waitpid() before passign on the SIGCONT. + * + * In any case nothing else is needed here. + */ + + /* Restore handler and signal mask */ + act.sa_handler = JobPassSig_suspend; + (void)sigaction(signo, &act, NULL); + (void)sigprocmask(SIG_SETMASK, &omask, NULL); +} + +/*- + *----------------------------------------------------------------------- + * JobFindPid -- + * Compare the pid of the job with the given pid and return 0 if they + * are equal. This function is called from Job_CatchChildren + * to find the job descriptor of the finished job. + * + * Input: + * job job to examine + * pid process id desired + * + * Results: + * Job with matching pid + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Job * +JobFindPid(int pid, int status, Boolean isJobs) +{ + Job *job; + + for (job = job_table; job < job_table_end; job++) { + if ((job->job_state == status) && job->pid == pid) + return job; + } + if (DEBUG(JOB) && isJobs) + job_table_dump("no pid"); + return NULL; +} + +/*- + *----------------------------------------------------------------------- + * JobPrintCommand -- + * Put out another command for the given job. If the command starts + * with an @ or a - we process it specially. In the former case, + * so long as the -s and -n flags weren't given to make, we stick + * a shell-specific echoOff command in the script. In the latter, + * we ignore errors for the entire job, unless the shell has error + * control. + * If the command is just "..." we take all future commands for this + * job to be commands to be executed once the entire graph has been + * made and return non-zero to signal that the end of the commands + * was reached. These commands are later attached to the postCommands + * node and executed by Job_End when all things are done. + * This function is called from JobStart via Lst_ForEach. + * + * Input: + * cmdp command string to print + * jobp job for which to print it + * + * Results: + * Always 0, unless the command was "..." + * + * Side Effects: + * If the command begins with a '-' and the shell has no error control, + * the JOB_IGNERR flag is set in the job descriptor. + * If the command is "..." and we're not ignoring such things, + * tailCmds is set to the successor node of the cmd. + * numCommands is incremented if the command is actually printed. + *----------------------------------------------------------------------- + */ +static int +JobPrintCommand(void *cmdp, void *jobp) +{ + Boolean noSpecials; /* true if we shouldn't worry about + * inserting special commands into + * the input stream. */ + Boolean shutUp = FALSE; /* true if we put a no echo command + * into the command file */ + Boolean errOff = FALSE; /* true if we turned error checking + * off before printing the command + * and need to turn it back on */ + const char *cmdTemplate; /* Template to use when printing the + * command */ + char *cmdStart; /* Start of expanded command */ + char *escCmd = NULL; /* Command with quotes/backticks escaped */ + char *cmd = (char *)cmdp; + Job *job = (Job *)jobp; + int i, j; + + noSpecials = NoExecute(job->node); + + if (strcmp(cmd, "...") == 0) { + job->node->type |= OP_SAVE_CMDS; + if ((job->flags & JOB_IGNDOTS) == 0) { + job->tailCmds = Lst_Succ(Lst_Member(job->node->commands, + cmd)); + return 1; + } + return 0; + } + +#define DBPRINTF(fmt, arg) if (DEBUG(JOB)) { \ + (void)fprintf(debug_file, fmt, arg); \ + } \ + (void)fprintf(job->cmdFILE, fmt, arg); \ + (void)fflush(job->cmdFILE); + + numCommands += 1; + + cmdStart = cmd = Var_Subst(NULL, cmd, job->node, VARF_WANTRES); + + cmdTemplate = "%s\n"; + + /* + * Check for leading @' and -'s to control echoing and error checking. + */ + while (*cmd == '@' || *cmd == '-' || (*cmd == '+')) { + switch (*cmd) { + case '@': + shutUp = DEBUG(LOUD) ? FALSE : TRUE; + break; + case '-': + errOff = TRUE; + break; + case '+': + if (noSpecials) { + /* + * We're not actually executing anything... + * but this one needs to be - use compat mode just for it. + */ + CompatRunCommand(cmdp, job->node); + free(cmdStart); + return 0; + } + break; + } + cmd++; + } + + while (isspace((unsigned char) *cmd)) + cmd++; + + /* + * If the shell doesn't have error control the alternate echo'ing will + * be done (to avoid showing additional error checking code) + * and this will need the characters '$ ` \ "' escaped + */ + + if (!commandShell->hasErrCtl) { + /* Worst that could happen is every char needs escaping. */ + escCmd = bmake_malloc((strlen(cmd) * 2) + 1); + for (i = 0, j= 0; cmd[i] != '\0'; i++, j++) { + if (cmd[i] == '$' || cmd[i] == '`' || cmd[i] == '\\' || + cmd[i] == '"') + escCmd[j++] = '\\'; + escCmd[j] = cmd[i]; + } + escCmd[j] = 0; + } + + if (shutUp) { + if (!(job->flags & JOB_SILENT) && !noSpecials && + commandShell->hasEchoCtl) { + DBPRINTF("%s\n", commandShell->echoOff); + } else { + if (commandShell->hasErrCtl) + shutUp = FALSE; + } + } + + if (errOff) { + if (!noSpecials) { + if (commandShell->hasErrCtl) { + /* + * we don't want the error-control commands showing + * up either, so we turn off echoing while executing + * them. We could put another field in the shell + * structure to tell JobDoOutput to look for this + * string too, but why make it any more complex than + * it already is? + */ + if (!(job->flags & JOB_SILENT) && !shutUp && + commandShell->hasEchoCtl) { + DBPRINTF("%s\n", commandShell->echoOff); + DBPRINTF("%s\n", commandShell->ignErr); + DBPRINTF("%s\n", commandShell->echoOn); + } else { + DBPRINTF("%s\n", commandShell->ignErr); + } + } else if (commandShell->ignErr && + (*commandShell->ignErr != '\0')) + { + /* + * The shell has no error control, so we need to be + * weird to get it to ignore any errors from the command. + * If echoing is turned on, we turn it off and use the + * errCheck template to echo the command. Leave echoing + * off so the user doesn't see the weirdness we go through + * to ignore errors. Set cmdTemplate to use the weirdness + * instead of the simple "%s\n" template. + */ + job->flags |= JOB_IGNERR; + if (!(job->flags & JOB_SILENT) && !shutUp) { + if (commandShell->hasEchoCtl) { + DBPRINTF("%s\n", commandShell->echoOff); + } + DBPRINTF(commandShell->errCheck, escCmd); + shutUp = TRUE; + } else { + if (!shutUp) { + DBPRINTF(commandShell->errCheck, escCmd); + } + } + cmdTemplate = commandShell->ignErr; + /* + * The error ignoration (hee hee) is already taken care + * of by the ignErr template, so pretend error checking + * is still on. + */ + errOff = FALSE; + } else { + errOff = FALSE; + } + } else { + errOff = FALSE; + } + } else { + + /* + * If errors are being checked and the shell doesn't have error control + * but does supply an errOut template, then setup commands to run + * through it. + */ + + if (!commandShell->hasErrCtl && commandShell->errOut && + (*commandShell->errOut != '\0')) { + if (!(job->flags & JOB_SILENT) && !shutUp) { + if (commandShell->hasEchoCtl) { + DBPRINTF("%s\n", commandShell->echoOff); + } + DBPRINTF(commandShell->errCheck, escCmd); + shutUp = TRUE; + } + /* If it's a comment line or blank, treat as an ignored error */ + if ((escCmd[0] == commandShell->commentChar) || + (escCmd[0] == 0)) + cmdTemplate = commandShell->ignErr; + else + cmdTemplate = commandShell->errOut; + errOff = FALSE; + } + } + + if (DEBUG(SHELL) && strcmp(shellName, "sh") == 0 && + (job->flags & JOB_TRACED) == 0) { + DBPRINTF("set -%s\n", "x"); + job->flags |= JOB_TRACED; + } + + DBPRINTF(cmdTemplate, cmd); + free(cmdStart); + free(escCmd); + if (errOff) { + /* + * If echoing is already off, there's no point in issuing the + * echoOff command. Otherwise we issue it and pretend it was on + * for the whole command... + */ + if (!shutUp && !(job->flags & JOB_SILENT) && commandShell->hasEchoCtl){ + DBPRINTF("%s\n", commandShell->echoOff); + shutUp = TRUE; + } + DBPRINTF("%s\n", commandShell->errCheck); + } + if (shutUp && commandShell->hasEchoCtl) { + DBPRINTF("%s\n", commandShell->echoOn); + } + return 0; +} + +/*- + *----------------------------------------------------------------------- + * JobSaveCommand -- + * Save a command to be executed when everything else is done. + * Callback function for JobFinish... + * + * Results: + * Always returns 0 + * + * Side Effects: + * The command is tacked onto the end of postCommands's commands list. + * + *----------------------------------------------------------------------- + */ +static int +JobSaveCommand(void *cmd, void *gn) +{ + cmd = Var_Subst(NULL, (char *)cmd, (GNode *)gn, VARF_WANTRES); + (void)Lst_AtEnd(postCommands->commands, cmd); + return(0); +} + + +/*- + *----------------------------------------------------------------------- + * JobClose -- + * Called to close both input and output pipes when a job is finished. + * + * Results: + * Nada + * + * Side Effects: + * The file descriptors associated with the job are closed. + * + *----------------------------------------------------------------------- + */ +static void +JobClose(Job *job) +{ + clearfd(job); + (void)close(job->outPipe); + job->outPipe = -1; + + JobDoOutput(job, TRUE); + (void)close(job->inPipe); + job->inPipe = -1; +} + +/*- + *----------------------------------------------------------------------- + * JobFinish -- + * Do final processing for the given job including updating + * parents and starting new jobs as available/necessary. Note + * that we pay no attention to the JOB_IGNERR flag here. + * This is because when we're called because of a noexecute flag + * or something, jstat.w_status is 0 and when called from + * Job_CatchChildren, the status is zeroed if it s/b ignored. + * + * Input: + * job job to finish + * status sub-why job went away + * + * Results: + * None + * + * Side Effects: + * Final commands for the job are placed on postCommands. + * + * If we got an error and are aborting (aborting == ABORT_ERROR) and + * the job list is now empty, we are done for the day. + * If we recognized an error (errors !=0), we set the aborting flag + * to ABORT_ERROR so no more jobs will be started. + *----------------------------------------------------------------------- + */ +/*ARGSUSED*/ +static void +JobFinish(Job *job, int status) +{ + Boolean done, return_job_token; + + if (DEBUG(JOB)) { + fprintf(debug_file, "Jobfinish: %d [%s], status %d\n", + job->pid, job->node->name, status); + } + + if ((WIFEXITED(status) && + (((WEXITSTATUS(status) != 0) && !(job->flags & JOB_IGNERR)))) || + WIFSIGNALED(status)) + { + /* + * If it exited non-zero and either we're doing things our + * way or we're not ignoring errors, the job is finished. + * Similarly, if the shell died because of a signal + * the job is also finished. In these + * cases, finish out the job's output before printing the exit + * status... + */ + JobClose(job); + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } + done = TRUE; + } else if (WIFEXITED(status)) { + /* + * Deal with ignored errors in -B mode. We need to print a message + * telling of the ignored error as well as setting status.w_status + * to 0 so the next command gets run. To do this, we set done to be + * TRUE if in -B mode and the job exited non-zero. + */ + done = WEXITSTATUS(status) != 0; + /* + * Old comment said: "Note we don't + * want to close down any of the streams until we know we're at the + * end." + * But we do. Otherwise when are we going to print the rest of the + * stuff? + */ + JobClose(job); + } else { + /* + * No need to close things down or anything. + */ + done = FALSE; + } + + if (done) { + if (WIFEXITED(status)) { + if (DEBUG(JOB)) { + (void)fprintf(debug_file, "Process %d [%s] exited.\n", + job->pid, job->node->name); + } + if (WEXITSTATUS(status) != 0) { + if (job->node != lastNode) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } +#ifdef USE_META + if (useMeta) { + meta_job_error(job, job->node, job->flags, WEXITSTATUS(status)); + } +#endif + (void)printf("*** [%s] Error code %d%s\n", + job->node->name, + WEXITSTATUS(status), + (job->flags & JOB_IGNERR) ? " (ignored)" : ""); + if (job->flags & JOB_IGNERR) { + status = 0; + } else { + if (deleteOnError) { + JobDeleteTarget(job->node); + } + PrintOnError(job->node, NULL); + } + } else if (DEBUG(JOB)) { + if (job->node != lastNode) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } + (void)printf("*** [%s] Completed successfully\n", + job->node->name); + } + } else { + if (job->node != lastNode) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } + (void)printf("*** [%s] Signal %d\n", + job->node->name, WTERMSIG(status)); + if (deleteOnError) { + JobDeleteTarget(job->node); + } + } + (void)fflush(stdout); + } + +#ifdef USE_META + if (useMeta) { + int x; + + if ((x = meta_job_finish(job)) != 0 && status == 0) { + status = x; + } + } +#endif + + return_job_token = FALSE; + + Trace_Log(JOBEND, job); + if (!(job->flags & JOB_SPECIAL)) { + if ((status != 0) || + (aborting == ABORT_ERROR) || + (aborting == ABORT_INTERRUPT)) + return_job_token = TRUE; + } + + if ((aborting != ABORT_ERROR) && (aborting != ABORT_INTERRUPT) && (status == 0)) { + /* + * As long as we aren't aborting and the job didn't return a non-zero + * status that we shouldn't ignore, we call Make_Update to update + * the parents. In addition, any saved commands for the node are placed + * on the .END target. + */ + if (job->tailCmds != NULL) { + Lst_ForEachFrom(job->node->commands, job->tailCmds, + JobSaveCommand, + job->node); + } + job->node->made = MADE; + if (!(job->flags & JOB_SPECIAL)) + return_job_token = TRUE; + Make_Update(job->node); + job->job_state = JOB_ST_FREE; + } else if (status != 0) { + errors += 1; + job->job_state = JOB_ST_FREE; + } + + /* + * Set aborting if any error. + */ + if (errors && !keepgoing && (aborting != ABORT_INTERRUPT)) { + /* + * If we found any errors in this batch of children and the -k flag + * wasn't given, we set the aborting flag so no more jobs get + * started. + */ + aborting = ABORT_ERROR; + } + + if (return_job_token) + Job_TokenReturn(); + + if (aborting == ABORT_ERROR && jobTokensRunning == 0) { + /* + * If we are aborting and the job table is now empty, we finish. + */ + Finish(errors); + } +} + +/*- + *----------------------------------------------------------------------- + * Job_Touch -- + * Touch the given target. Called by JobStart when the -t flag was + * given + * + * Input: + * gn the node of the file to touch + * silent TRUE if should not print message + * + * Results: + * None + * + * Side Effects: + * The data modification of the file is changed. In addition, if the + * file did not exist, it is created. + *----------------------------------------------------------------------- + */ +void +Job_Touch(GNode *gn, Boolean silent) +{ + int streamID; /* ID of stream opened to do the touch */ + struct utimbuf times; /* Times for utime() call */ + + if (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC|OP_OPTIONAL| + OP_SPECIAL|OP_PHONY)) { + /* + * .JOIN, .USE, .ZEROTIME and .OPTIONAL targets are "virtual" targets + * and, as such, shouldn't really be created. + */ + return; + } + + if (!silent || NoExecute(gn)) { + (void)fprintf(stdout, "touch %s\n", gn->name); + (void)fflush(stdout); + } + + if (NoExecute(gn)) { + return; + } + + if (gn->type & OP_ARCHV) { + Arch_Touch(gn); + } else if (gn->type & OP_LIB) { + Arch_TouchLib(gn); + } else { + char *file = gn->path ? gn->path : gn->name; + + times.actime = times.modtime = now; + if (utime(file, ×) < 0){ + streamID = open(file, O_RDWR | O_CREAT, 0666); + + if (streamID >= 0) { + char c; + + /* + * Read and write a byte to the file to change the + * modification time, then close the file. + */ + if (read(streamID, &c, 1) == 1) { + (void)lseek(streamID, (off_t)0, SEEK_SET); + while (write(streamID, &c, 1) == -1 && errno == EAGAIN) + continue; + } + + (void)close(streamID); + } else { + (void)fprintf(stdout, "*** couldn't touch %s: %s", + file, strerror(errno)); + (void)fflush(stdout); + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * Job_CheckCommands -- + * Make sure the given node has all the commands it needs. + * + * Input: + * gn The target whose commands need verifying + * abortProc Function to abort with message + * + * Results: + * TRUE if the commands list is/was ok. + * + * Side Effects: + * The node will have commands from the .DEFAULT rule added to it + * if it needs them. + *----------------------------------------------------------------------- + */ +Boolean +Job_CheckCommands(GNode *gn, void (*abortProc)(const char *, ...)) +{ + if (OP_NOP(gn->type) && Lst_IsEmpty(gn->commands) && + ((gn->type & OP_LIB) == 0 || Lst_IsEmpty(gn->children))) { + /* + * No commands. Look for .DEFAULT rule from which we might infer + * commands + */ + if ((DEFAULT != NULL) && !Lst_IsEmpty(DEFAULT->commands) && + (gn->type & OP_SPECIAL) == 0) { + char *p1; + /* + * Make only looks for a .DEFAULT if the node was never the + * target of an operator, so that's what we do too. If + * a .DEFAULT was given, we substitute its commands for gn's + * commands and set the IMPSRC variable to be the target's name + * The DEFAULT node acts like a transformation rule, in that + * gn also inherits any attributes or sources attached to + * .DEFAULT itself. + */ + Make_HandleUse(DEFAULT, gn); + Var_Set(IMPSRC, Var_Value(TARGET, gn, &p1), gn, 0); + free(p1); + } else if (Dir_MTime(gn, 0) == 0 && (gn->type & OP_SPECIAL) == 0) { + /* + * The node wasn't the target of an operator we have no .DEFAULT + * rule to go on and the target doesn't already exist. There's + * nothing more we can do for this branch. If the -k flag wasn't + * given, we stop in our tracks, otherwise we just don't update + * this node's parents so they never get examined. + */ + static const char msg[] = ": don't know how to make"; + + if (gn->flags & FROM_DEPEND) { + if (!Job_RunTarget(".STALE", gn->fname)) + fprintf(stdout, "%s: %s, %d: ignoring stale %s for %s\n", + progname, gn->fname, gn->lineno, makeDependfile, + gn->name); + return TRUE; + } + + if (gn->type & OP_OPTIONAL) { + (void)fprintf(stdout, "%s%s %s (ignored)\n", progname, + msg, gn->name); + (void)fflush(stdout); + } else if (keepgoing) { + (void)fprintf(stdout, "%s%s %s (continuing)\n", progname, + msg, gn->name); + (void)fflush(stdout); + return FALSE; + } else { + (*abortProc)("%s%s %s. Stop", progname, msg, gn->name); + return FALSE; + } + } + } + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * JobExec -- + * Execute the shell for the given job. Called from JobStart + * + * Input: + * job Job to execute + * + * Results: + * None. + * + * Side Effects: + * A shell is executed, outputs is altered and the Job structure added + * to the job table. + * + *----------------------------------------------------------------------- + */ +static void +JobExec(Job *job, char **argv) +{ + int cpid; /* ID of new child */ + sigset_t mask; + + job->flags &= ~JOB_TRACED; + + if (DEBUG(JOB)) { + int i; + + (void)fprintf(debug_file, "Running %s %sly\n", job->node->name, "local"); + (void)fprintf(debug_file, "\tCommand: "); + for (i = 0; argv[i] != NULL; i++) { + (void)fprintf(debug_file, "%s ", argv[i]); + } + (void)fprintf(debug_file, "\n"); + } + + /* + * Some jobs produce no output and it's disconcerting to have + * no feedback of their running (since they produce no output, the + * banner with their name in it never appears). This is an attempt to + * provide that feedback, even if nothing follows it. + */ + if ((lastNode != job->node) && !(job->flags & JOB_SILENT)) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } + + /* No interruptions until this job is on the `jobs' list */ + JobSigLock(&mask); + + /* Pre-emptively mark job running, pid still zero though */ + job->job_state = JOB_ST_RUNNING; + + cpid = vFork(); + if (cpid == -1) + Punt("Cannot vfork: %s", strerror(errno)); + + if (cpid == 0) { + /* Child */ + sigset_t tmask; + +#ifdef USE_META + if (useMeta) { + meta_job_child(job); + } +#endif + /* + * Reset all signal handlers; this is necessary because we also + * need to unblock signals before we exec(2). + */ + JobSigReset(); + + /* Now unblock signals */ + sigemptyset(&tmask); + JobSigUnlock(&tmask); + + /* + * Must duplicate the input stream down to the child's input and + * reset it to the beginning (again). Since the stream was marked + * close-on-exec, we must clear that bit in the new input. + */ + if (dup2(FILENO(job->cmdFILE), 0) == -1) { + execError("dup2", "job->cmdFILE"); + _exit(1); + } + if (fcntl(0, F_SETFD, 0) == -1) { + execError("fcntl clear close-on-exec", "stdin"); + _exit(1); + } + if (lseek(0, (off_t)0, SEEK_SET) == -1) { + execError("lseek to 0", "stdin"); + _exit(1); + } + + if (job->node->type & (OP_MAKE | OP_SUBMAKE)) { + /* + * Pass job token pipe to submakes. + */ + if (fcntl(tokenWaitJob.inPipe, F_SETFD, 0) == -1) { + execError("clear close-on-exec", "tokenWaitJob.inPipe"); + _exit(1); + } + if (fcntl(tokenWaitJob.outPipe, F_SETFD, 0) == -1) { + execError("clear close-on-exec", "tokenWaitJob.outPipe"); + _exit(1); + } + } + + /* + * Set up the child's output to be routed through the pipe + * we've created for it. + */ + if (dup2(job->outPipe, 1) == -1) { + execError("dup2", "job->outPipe"); + _exit(1); + } + /* + * The output channels are marked close on exec. This bit was + * duplicated by the dup2(on some systems), so we have to clear + * it before routing the shell's error output to the same place as + * its standard output. + */ + if (fcntl(1, F_SETFD, 0) == -1) { + execError("clear close-on-exec", "stdout"); + _exit(1); + } + if (dup2(1, 2) == -1) { + execError("dup2", "1, 2"); + _exit(1); + } + + /* + * We want to switch the child into a different process family so + * we can kill it and all its descendants in one fell swoop, + * by killing its process family, but not commit suicide. + */ +#if defined(MAKE_NATIVE) || defined(HAVE_SETPGID) +#if defined(SYSV) + /* XXX: dsl - I'm sure this should be setpgrp()... */ + (void)setsid(); +#else + (void)setpgid(0, getpid()); +#endif +#endif + + Var_ExportVars(); + + (void)execv(shellPath, argv); + execError("exec", shellPath); + _exit(1); + } + + /* Parent, continuing after the child exec */ + job->pid = cpid; + + Trace_Log(JOBSTART, job); + + /* + * Set the current position in the buffer to the beginning + * and mark another stream to watch in the outputs mask + */ + job->curPos = 0; + + watchfd(job); + + if (job->cmdFILE != NULL && job->cmdFILE != stdout) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } + + /* + * Now the job is actually running, add it to the table. + */ + if (DEBUG(JOB)) { + fprintf(debug_file, "JobExec(%s): pid %d added to jobs table\n", + job->node->name, job->pid); + job_table_dump("job started"); + } + JobSigUnlock(&mask); +} + +/*- + *----------------------------------------------------------------------- + * JobMakeArgv -- + * Create the argv needed to execute the shell for a given job. + * + * + * Results: + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +static void +JobMakeArgv(Job *job, char **argv) +{ + int argc; + static char args[10]; /* For merged arguments */ + + argv[0] = UNCONST(shellName); + argc = 1; + + if ((commandShell->exit && (*commandShell->exit != '-')) || + (commandShell->echo && (*commandShell->echo != '-'))) + { + /* + * At least one of the flags doesn't have a minus before it, so + * merge them together. Have to do this because the *(&(@*#*&#$# + * Bourne shell thinks its second argument is a file to source. + * Grrrr. Note the ten-character limitation on the combined arguments. + */ + (void)snprintf(args, sizeof(args), "-%s%s", + ((job->flags & JOB_IGNERR) ? "" : + (commandShell->exit ? commandShell->exit : "")), + ((job->flags & JOB_SILENT) ? "" : + (commandShell->echo ? commandShell->echo : ""))); + + if (args[1]) { + argv[argc] = args; + argc++; + } + } else { + if (!(job->flags & JOB_IGNERR) && commandShell->exit) { + argv[argc] = UNCONST(commandShell->exit); + argc++; + } + if (!(job->flags & JOB_SILENT) && commandShell->echo) { + argv[argc] = UNCONST(commandShell->echo); + argc++; + } + } + argv[argc] = NULL; +} + +/*- + *----------------------------------------------------------------------- + * JobStart -- + * Start a target-creation process going for the target described + * by the graph node gn. + * + * Input: + * gn target to create + * flags flags for the job to override normal ones. + * e.g. JOB_SPECIAL or JOB_IGNDOTS + * previous The previous Job structure for this node, if any. + * + * Results: + * JOB_ERROR if there was an error in the commands, JOB_FINISHED + * if there isn't actually anything left to do for the job and + * JOB_RUNNING if the job has been started. + * + * Side Effects: + * A new Job node is created and added to the list of running + * jobs. PMake is forked and a child shell created. + * + * NB: I'm fairly sure that this code is never called with JOB_SPECIAL set + * JOB_IGNDOTS is never set (dsl) + * Also the return value is ignored by everyone. + *----------------------------------------------------------------------- + */ +static int +JobStart(GNode *gn, int flags) +{ + Job *job; /* new job descriptor */ + char *argv[10]; /* Argument vector to shell */ + Boolean cmdsOK; /* true if the nodes commands were all right */ + Boolean noExec; /* Set true if we decide not to run the job */ + int tfd; /* File descriptor to the temp file */ + + for (job = job_table; job < job_table_end; job++) { + if (job->job_state == JOB_ST_FREE) + break; + } + if (job >= job_table_end) + Punt("JobStart no job slots vacant"); + + memset(job, 0, sizeof *job); + job->job_state = JOB_ST_SETUP; + if (gn->type & OP_SPECIAL) + flags |= JOB_SPECIAL; + + job->node = gn; + job->tailCmds = NULL; + + /* + * Set the initial value of the flags for this job based on the global + * ones and the node's attributes... Any flags supplied by the caller + * are also added to the field. + */ + job->flags = 0; + if (Targ_Ignore(gn)) { + job->flags |= JOB_IGNERR; + } + if (Targ_Silent(gn)) { + job->flags |= JOB_SILENT; + } + job->flags |= flags; + + /* + * Check the commands now so any attributes from .DEFAULT have a chance + * to migrate to the node + */ + cmdsOK = Job_CheckCommands(gn, Error); + + job->inPollfd = NULL; + /* + * If the -n flag wasn't given, we open up OUR (not the child's) + * temporary file to stuff commands in it. The thing is rd/wr so we don't + * need to reopen it to feed it to the shell. If the -n flag *was* given, + * we just set the file to be stdout. Cute, huh? + */ + if (((gn->type & OP_MAKE) && !(noRecursiveExecute)) || + (!noExecute && !touchFlag)) { + /* + * tfile is the name of a file into which all shell commands are + * put. It is removed before the child shell is executed, unless + * DEBUG(SCRIPT) is set. + */ + char *tfile; + sigset_t mask; + /* + * We're serious here, but if the commands were bogus, we're + * also dead... + */ + if (!cmdsOK) { + PrintOnError(gn, NULL); /* provide some clue */ + DieHorribly(); + } + + JobSigLock(&mask); + tfd = mkTempFile(TMPPAT, &tfile); + if (!DEBUG(SCRIPT)) + (void)eunlink(tfile); + JobSigUnlock(&mask); + + job->cmdFILE = fdopen(tfd, "w+"); + if (job->cmdFILE == NULL) { + Punt("Could not fdopen %s", tfile); + } + (void)fcntl(FILENO(job->cmdFILE), F_SETFD, FD_CLOEXEC); + /* + * Send the commands to the command file, flush all its buffers then + * rewind and remove the thing. + */ + noExec = FALSE; + +#ifdef USE_META + if (useMeta) { + meta_job_start(job, gn); + if (Targ_Silent(gn)) { /* might have changed */ + job->flags |= JOB_SILENT; + } + } +#endif + /* + * We can do all the commands at once. hooray for sanity + */ + numCommands = 0; + Lst_ForEach(gn->commands, JobPrintCommand, job); + + /* + * If we didn't print out any commands to the shell script, + * there's not much point in executing the shell, is there? + */ + if (numCommands == 0) { + noExec = TRUE; + } + + free(tfile); + } else if (NoExecute(gn)) { + /* + * Not executing anything -- just print all the commands to stdout + * in one fell swoop. This will still set up job->tailCmds correctly. + */ + if (lastNode != gn) { + MESSAGE(stdout, gn); + lastNode = gn; + } + job->cmdFILE = stdout; + /* + * Only print the commands if they're ok, but don't die if they're + * not -- just let the user know they're bad and keep going. It + * doesn't do any harm in this case and may do some good. + */ + if (cmdsOK) { + Lst_ForEach(gn->commands, JobPrintCommand, job); + } + /* + * Don't execute the shell, thank you. + */ + noExec = TRUE; + } else { + /* + * Just touch the target and note that no shell should be executed. + * Set cmdFILE to stdout to make life easier. Check the commands, too, + * but don't die if they're no good -- it does no harm to keep working + * up the graph. + */ + job->cmdFILE = stdout; + Job_Touch(gn, job->flags&JOB_SILENT); + noExec = TRUE; + } + /* Just in case it isn't already... */ + (void)fflush(job->cmdFILE); + + /* + * If we're not supposed to execute a shell, don't. + */ + if (noExec) { + if (!(job->flags & JOB_SPECIAL)) + Job_TokenReturn(); + /* + * Unlink and close the command file if we opened one + */ + if (job->cmdFILE != stdout) { + if (job->cmdFILE != NULL) { + (void)fclose(job->cmdFILE); + job->cmdFILE = NULL; + } + } + + /* + * We only want to work our way up the graph if we aren't here because + * the commands for the job were no good. + */ + if (cmdsOK && aborting == 0) { + if (job->tailCmds != NULL) { + Lst_ForEachFrom(job->node->commands, job->tailCmds, + JobSaveCommand, + job->node); + } + job->node->made = MADE; + Make_Update(job->node); + } + job->job_state = JOB_ST_FREE; + return cmdsOK ? JOB_FINISHED : JOB_ERROR; + } + + /* + * Set up the control arguments to the shell. This is based on the flags + * set earlier for this job. + */ + JobMakeArgv(job, argv); + + /* Create the pipe by which we'll get the shell's output. */ + JobCreatePipe(job, 3); + + JobExec(job, argv); + return(JOB_RUNNING); +} + +static char * +JobOutput(Job *job, char *cp, char *endp, int msg) +{ + char *ecp; + + if (commandShell->noPrint) { + ecp = Str_FindSubstring(cp, commandShell->noPrint); + while (ecp != NULL) { + if (cp != ecp) { + *ecp = '\0'; + if (!beSilent && msg && job->node != lastNode) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } + /* + * The only way there wouldn't be a newline after + * this line is if it were the last in the buffer. + * however, since the non-printable comes after it, + * there must be a newline, so we don't print one. + */ + (void)fprintf(stdout, "%s", cp); + (void)fflush(stdout); + } + cp = ecp + commandShell->noPLen; + if (cp != endp) { + /* + * Still more to print, look again after skipping + * the whitespace following the non-printable + * command.... + */ + cp++; + while (*cp == ' ' || *cp == '\t' || *cp == '\n') { + cp++; + } + ecp = Str_FindSubstring(cp, commandShell->noPrint); + } else { + return cp; + } + } + } + return cp; +} + +/*- + *----------------------------------------------------------------------- + * JobDoOutput -- + * This function is called at different times depending on + * whether the user has specified that output is to be collected + * via pipes or temporary files. In the former case, we are called + * whenever there is something to read on the pipe. We collect more + * output from the given job and store it in the job's outBuf. If + * this makes up a line, we print it tagged by the job's identifier, + * as necessary. + * If output has been collected in a temporary file, we open the + * file and read it line by line, transfering it to our own + * output channel until the file is empty. At which point we + * remove the temporary file. + * In both cases, however, we keep our figurative eye out for the + * 'noPrint' line for the shell from which the output came. If + * we recognize a line, we don't print it. If the command is not + * alone on the line (the character after it is not \0 or \n), we + * do print whatever follows it. + * + * Input: + * job the job whose output needs printing + * finish TRUE if this is the last time we'll be called + * for this job + * + * Results: + * None + * + * Side Effects: + * curPos may be shifted as may the contents of outBuf. + *----------------------------------------------------------------------- + */ +STATIC void +JobDoOutput(Job *job, Boolean finish) +{ + Boolean gotNL = FALSE; /* true if got a newline */ + Boolean fbuf; /* true if our buffer filled up */ + int nr; /* number of bytes read */ + int i; /* auxiliary index into outBuf */ + int max; /* limit for i (end of current data) */ + int nRead; /* (Temporary) number of bytes read */ + + /* + * Read as many bytes as will fit in the buffer. + */ +end_loop: + gotNL = FALSE; + fbuf = FALSE; + + nRead = read(job->inPipe, &job->outBuf[job->curPos], + JOB_BUFSIZE - job->curPos); + if (nRead < 0) { + if (errno == EAGAIN) + return; + if (DEBUG(JOB)) { + perror("JobDoOutput(piperead)"); + } + nr = 0; + } else { + nr = nRead; + } + + /* + * If we hit the end-of-file (the job is dead), we must flush its + * remaining output, so pretend we read a newline if there's any + * output remaining in the buffer. + * Also clear the 'finish' flag so we stop looping. + */ + if ((nr == 0) && (job->curPos != 0)) { + job->outBuf[job->curPos] = '\n'; + nr = 1; + finish = FALSE; + } else if (nr == 0) { + finish = FALSE; + } + + /* + * Look for the last newline in the bytes we just got. If there is + * one, break out of the loop with 'i' as its index and gotNL set + * TRUE. + */ + max = job->curPos + nr; + for (i = job->curPos + nr - 1; i >= job->curPos; i--) { + if (job->outBuf[i] == '\n') { + gotNL = TRUE; + break; + } else if (job->outBuf[i] == '\0') { + /* + * Why? + */ + job->outBuf[i] = ' '; + } + } + + if (!gotNL) { + job->curPos += nr; + if (job->curPos == JOB_BUFSIZE) { + /* + * If we've run out of buffer space, we have no choice + * but to print the stuff. sigh. + */ + fbuf = TRUE; + i = job->curPos; + } + } + if (gotNL || fbuf) { + /* + * Need to send the output to the screen. Null terminate it + * first, overwriting the newline character if there was one. + * So long as the line isn't one we should filter (according + * to the shell description), we print the line, preceded + * by a target banner if this target isn't the same as the + * one for which we last printed something. + * The rest of the data in the buffer are then shifted down + * to the start of the buffer and curPos is set accordingly. + */ + job->outBuf[i] = '\0'; + if (i >= job->curPos) { + char *cp; + + cp = JobOutput(job, job->outBuf, &job->outBuf[i], FALSE); + + /* + * There's still more in that thar buffer. This time, though, + * we know there's no newline at the end, so we add one of + * our own free will. + */ + if (*cp != '\0') { + if (!beSilent && job->node != lastNode) { + MESSAGE(stdout, job->node); + lastNode = job->node; + } +#ifdef USE_META + if (useMeta) { + meta_job_output(job, cp, gotNL ? "\n" : ""); + } +#endif + (void)fprintf(stdout, "%s%s", cp, gotNL ? "\n" : ""); + (void)fflush(stdout); + } + } + /* + * max is the last offset still in the buffer. Move any remaining + * characters to the start of the buffer and update the end marker + * curPos. + */ + if (i < max) { + (void)memmove(job->outBuf, &job->outBuf[i + 1], max - (i + 1)); + job->curPos = max - (i + 1); + } else { + assert(i == max); + job->curPos = 0; + } + } + if (finish) { + /* + * If the finish flag is true, we must loop until we hit + * end-of-file on the pipe. This is guaranteed to happen + * eventually since the other end of the pipe is now closed + * (we closed it explicitly and the child has exited). When + * we do get an EOF, finish will be set FALSE and we'll fall + * through and out. + */ + goto end_loop; + } +} + +static void +JobRun(GNode *targ) +{ +#ifdef notyet + /* + * Unfortunately it is too complicated to run .BEGIN, .END, + * and .INTERRUPT job in the parallel job module. This has + * the nice side effect that it avoids a lot of other problems. + */ + Lst lst = Lst_Init(FALSE); + Lst_AtEnd(lst, targ); + (void)Make_Run(lst); + Lst_Destroy(lst, NULL); + JobStart(targ, JOB_SPECIAL); + while (jobTokensRunning) { + Job_CatchOutput(); + } +#else + Compat_Make(targ, targ); + if (targ->made == ERROR) { + PrintOnError(targ, "\n\nStop."); + exit(1); + } +#endif +} + +/*- + *----------------------------------------------------------------------- + * Job_CatchChildren -- + * Handle the exit of a child. Called from Make_Make. + * + * Input: + * block TRUE if should block on the wait + * + * Results: + * none. + * + * Side Effects: + * The job descriptor is removed from the list of children. + * + * Notes: + * We do waits, blocking or not, according to the wisdom of our + * caller, until there are no more children to report. For each + * job, call JobFinish to finish things off. + * + *----------------------------------------------------------------------- + */ + +void +Job_CatchChildren(void) +{ + int pid; /* pid of dead child */ + int status; /* Exit/termination status */ + + /* + * Don't even bother if we know there's no one around. + */ + if (jobTokensRunning == 0) + return; + + while ((pid = waitpid((pid_t) -1, &status, WNOHANG | WUNTRACED)) > 0) { + if (DEBUG(JOB)) { + (void)fprintf(debug_file, "Process %d exited/stopped status %x.\n", pid, + status); + } + JobReapChild(pid, status, TRUE); + } +} + +/* + * It is possible that wait[pid]() was called from elsewhere, + * this lets us reap jobs regardless. + */ +void +JobReapChild(pid_t pid, int status, Boolean isJobs) +{ + Job *job; /* job descriptor for dead child */ + + /* + * Don't even bother if we know there's no one around. + */ + if (jobTokensRunning == 0) + return; + + job = JobFindPid(pid, JOB_ST_RUNNING, isJobs); + if (job == NULL) { + if (isJobs) { + if (!lurking_children) + Error("Child (%d) status %x not in table?", pid, status); + } + return; /* not ours */ + } + if (WIFSTOPPED(status)) { + if (DEBUG(JOB)) { + (void)fprintf(debug_file, "Process %d (%s) stopped.\n", + job->pid, job->node->name); + } + if (!make_suspended) { + switch (WSTOPSIG(status)) { + case SIGTSTP: + (void)printf("*** [%s] Suspended\n", job->node->name); + break; + case SIGSTOP: + (void)printf("*** [%s] Stopped\n", job->node->name); + break; + default: + (void)printf("*** [%s] Stopped -- signal %d\n", + job->node->name, WSTOPSIG(status)); + } + job->job_suspended = 1; + } + (void)fflush(stdout); + return; + } + + job->job_state = JOB_ST_FINISHED; + job->exit_status = status; + + JobFinish(job, status); +} + +/*- + *----------------------------------------------------------------------- + * Job_CatchOutput -- + * Catch the output from our children, if we're using + * pipes do so. Otherwise just block time until we get a + * signal(most likely a SIGCHLD) since there's no point in + * just spinning when there's nothing to do and the reaping + * of a child can wait for a while. + * + * Results: + * None + * + * Side Effects: + * Output is read from pipes if we're piping. + * ----------------------------------------------------------------------- + */ +void +Job_CatchOutput(void) +{ + int nready; + Job *job; + int i; + + (void)fflush(stdout); + + /* The first fd in the list is the job token pipe */ + do { + nready = poll(fds + 1 - wantToken, nfds - 1 + wantToken, POLL_MSEC); + } while (nready < 0 && errno == EINTR); + + if (nready < 0) + Punt("poll: %s", strerror(errno)); + + if (nready > 0 && readyfd(&childExitJob)) { + char token = 0; + ssize_t count; + count = read(childExitJob.inPipe, &token, 1); + switch (count) { + case 0: + Punt("unexpected eof on token pipe"); + case -1: + Punt("token pipe read: %s", strerror(errno)); + case 1: + if (token == DO_JOB_RESUME[0]) + /* Complete relay requested from our SIGCONT handler */ + JobRestartJobs(); + break; + default: + abort(); + } + --nready; + } + + Job_CatchChildren(); + if (nready == 0) + return; + + for (i = 2; i < nfds; i++) { + if (!fds[i].revents) + continue; + job = jobfds[i]; + if (job->job_state == JOB_ST_RUNNING) + JobDoOutput(job, FALSE); + if (--nready == 0) + return; + } +} + +/*- + *----------------------------------------------------------------------- + * Job_Make -- + * Start the creation of a target. Basically a front-end for + * JobStart used by the Make module. + * + * Results: + * None. + * + * Side Effects: + * Another job is started. + * + *----------------------------------------------------------------------- + */ +void +Job_Make(GNode *gn) +{ + (void)JobStart(gn, 0); +} + +void +Shell_Init(void) +{ + if (shellPath == NULL) { + /* + * We are using the default shell, which may be an absolute + * path if DEFSHELL_CUSTOM is defined. + */ + shellName = commandShell->name; +#ifdef DEFSHELL_CUSTOM + if (*shellName == '/') { + shellPath = shellName; + shellName = strrchr(shellPath, '/'); + shellName++; + } else +#endif + shellPath = str_concat(_PATH_DEFSHELLDIR, shellName, STR_ADDSLASH); + } + if (commandShell->exit == NULL) { + commandShell->exit = ""; + } + if (commandShell->echo == NULL) { + commandShell->echo = ""; + } + if (commandShell->hasErrCtl && *commandShell->exit) { + if (shellErrFlag && + strcmp(commandShell->exit, &shellErrFlag[1]) != 0) { + free(shellErrFlag); + shellErrFlag = NULL; + } + if (!shellErrFlag) { + int n = strlen(commandShell->exit) + 2; + + shellErrFlag = bmake_malloc(n); + if (shellErrFlag) { + snprintf(shellErrFlag, n, "-%s", commandShell->exit); + } + } + } else if (shellErrFlag) { + free(shellErrFlag); + shellErrFlag = NULL; + } +} + +/*- + * Returns the string literal that is used in the current command shell + * to produce a newline character. + */ +const char * +Shell_GetNewline(void) +{ + + return commandShell->newline; +} + +void +Job_SetPrefix(void) +{ + + if (targPrefix) { + free(targPrefix); + } else if (!Var_Exists(MAKE_JOB_PREFIX, VAR_GLOBAL)) { + Var_Set(MAKE_JOB_PREFIX, "---", VAR_GLOBAL, 0); + } + + targPrefix = Var_Subst(NULL, "${" MAKE_JOB_PREFIX "}", + VAR_GLOBAL, VARF_WANTRES); +} + +/*- + *----------------------------------------------------------------------- + * Job_Init -- + * Initialize the process module + * + * Input: + * + * Results: + * none + * + * Side Effects: + * lists and counters are initialized + *----------------------------------------------------------------------- + */ +void +Job_Init(void) +{ + Job_SetPrefix(); + /* Allocate space for all the job info */ + job_table = bmake_malloc(maxJobs * sizeof *job_table); + memset(job_table, 0, maxJobs * sizeof *job_table); + job_table_end = job_table + maxJobs; + wantToken = 0; + + aborting = 0; + errors = 0; + + lastNode = NULL; + + /* + * There is a non-zero chance that we already have children. + * eg after 'make -f- < 0) + continue; + if (rval == 0) + lurking_children = 1; + break; + } + + Shell_Init(); + + JobCreatePipe(&childExitJob, 3); + + /* We can only need to wait for tokens, children and output from each job */ + fds = bmake_malloc(sizeof (*fds) * (2 + maxJobs)); + jobfds = bmake_malloc(sizeof (*jobfds) * (2 + maxJobs)); + + /* These are permanent entries and take slots 0 and 1 */ + watchfd(&tokenWaitJob); + watchfd(&childExitJob); + + sigemptyset(&caught_signals); + /* + * Install a SIGCHLD handler. + */ + (void)bmake_signal(SIGCHLD, JobChildSig); + sigaddset(&caught_signals, SIGCHLD); + +#define ADDSIG(s,h) \ + if (bmake_signal(s, SIG_IGN) != SIG_IGN) { \ + sigaddset(&caught_signals, s); \ + (void)bmake_signal(s, h); \ + } + + /* + * Catch the four signals that POSIX specifies if they aren't ignored. + * JobPassSig will take care of calling JobInterrupt if appropriate. + */ + ADDSIG(SIGINT, JobPassSig_int) + ADDSIG(SIGHUP, JobPassSig_term) + ADDSIG(SIGTERM, JobPassSig_term) + ADDSIG(SIGQUIT, JobPassSig_term) + + /* + * There are additional signals that need to be caught and passed if + * either the export system wants to be told directly of signals or if + * we're giving each job its own process group (since then it won't get + * signals from the terminal driver as we own the terminal) + */ + ADDSIG(SIGTSTP, JobPassSig_suspend) + ADDSIG(SIGTTOU, JobPassSig_suspend) + ADDSIG(SIGTTIN, JobPassSig_suspend) + ADDSIG(SIGWINCH, JobCondPassSig) + ADDSIG(SIGCONT, JobContinueSig) +#undef ADDSIG + + (void)Job_RunTarget(".BEGIN", NULL); + postCommands = Targ_FindNode(".END", TARG_CREATE); +} + +static void JobSigReset(void) +{ +#define DELSIG(s) \ + if (sigismember(&caught_signals, s)) { \ + (void)bmake_signal(s, SIG_DFL); \ + } + + DELSIG(SIGINT) + DELSIG(SIGHUP) + DELSIG(SIGQUIT) + DELSIG(SIGTERM) + DELSIG(SIGTSTP) + DELSIG(SIGTTOU) + DELSIG(SIGTTIN) + DELSIG(SIGWINCH) + DELSIG(SIGCONT) +#undef DELSIG + (void)bmake_signal(SIGCHLD, SIG_DFL); +} + +/*- + *----------------------------------------------------------------------- + * JobMatchShell -- + * Find a shell in 'shells' given its name. + * + * Results: + * A pointer to the Shell structure. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Shell * +JobMatchShell(const char *name) +{ + Shell *sh; + + for (sh = shells; sh->name != NULL; sh++) { + if (strcmp(name, sh->name) == 0) + return (sh); + } + return NULL; +} + +/*- + *----------------------------------------------------------------------- + * Job_ParseShell -- + * Parse a shell specification and set up commandShell, shellPath + * and shellName appropriately. + * + * Input: + * line The shell spec + * + * Results: + * FAILURE if the specification was incorrect. + * + * Side Effects: + * commandShell points to a Shell structure (either predefined or + * created from the shell spec), shellPath is the full path of the + * shell described by commandShell, while shellName is just the + * final component of shellPath. + * + * Notes: + * A shell specification consists of a .SHELL target, with dependency + * operator, followed by a series of blank-separated words. Double + * quotes can be used to use blanks in words. A backslash escapes + * anything (most notably a double-quote and a space) and + * provides the functionality it does in C. Each word consists of + * keyword and value separated by an equal sign. There should be no + * unnecessary spaces in the word. The keywords are as follows: + * name Name of shell. + * path Location of shell. + * quiet Command to turn off echoing. + * echo Command to turn echoing on + * filter Result of turning off echoing that shouldn't be + * printed. + * echoFlag Flag to turn echoing on at the start + * errFlag Flag to turn error checking on at the start + * hasErrCtl True if shell has error checking control + * newline String literal to represent a newline char + * check Command to turn on error checking if hasErrCtl + * is TRUE or template of command to echo a command + * for which error checking is off if hasErrCtl is + * FALSE. + * ignore Command to turn off error checking if hasErrCtl + * is TRUE or template of command to execute a + * command so as to ignore any errors it returns if + * hasErrCtl is FALSE. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Job_ParseShell(char *line) +{ + char **words; + char **argv; + int argc; + char *path; + Shell newShell; + Boolean fullSpec = FALSE; + Shell *sh; + + while (isspace((unsigned char)*line)) { + line++; + } + + free(UNCONST(shellArgv)); + + memset(&newShell, 0, sizeof(newShell)); + + /* + * Parse the specification by keyword + */ + words = brk_string(line, &argc, TRUE, &path); + if (words == NULL) { + Error("Unterminated quoted string [%s]", line); + return FAILURE; + } + shellArgv = path; + + for (path = NULL, argv = words; argc != 0; argc--, argv++) { + if (strncmp(*argv, "path=", 5) == 0) { + path = &argv[0][5]; + } else if (strncmp(*argv, "name=", 5) == 0) { + newShell.name = &argv[0][5]; + } else { + if (strncmp(*argv, "quiet=", 6) == 0) { + newShell.echoOff = &argv[0][6]; + } else if (strncmp(*argv, "echo=", 5) == 0) { + newShell.echoOn = &argv[0][5]; + } else if (strncmp(*argv, "filter=", 7) == 0) { + newShell.noPrint = &argv[0][7]; + newShell.noPLen = strlen(newShell.noPrint); + } else if (strncmp(*argv, "echoFlag=", 9) == 0) { + newShell.echo = &argv[0][9]; + } else if (strncmp(*argv, "errFlag=", 8) == 0) { + newShell.exit = &argv[0][8]; + } else if (strncmp(*argv, "hasErrCtl=", 10) == 0) { + char c = argv[0][10]; + newShell.hasErrCtl = !((c != 'Y') && (c != 'y') && + (c != 'T') && (c != 't')); + } else if (strncmp(*argv, "newline=", 8) == 0) { + newShell.newline = &argv[0][8]; + } else if (strncmp(*argv, "check=", 6) == 0) { + newShell.errCheck = &argv[0][6]; + } else if (strncmp(*argv, "ignore=", 7) == 0) { + newShell.ignErr = &argv[0][7]; + } else if (strncmp(*argv, "errout=", 7) == 0) { + newShell.errOut = &argv[0][7]; + } else if (strncmp(*argv, "comment=", 8) == 0) { + newShell.commentChar = argv[0][8]; + } else { + Parse_Error(PARSE_FATAL, "Unknown keyword \"%s\"", + *argv); + free(words); + return(FAILURE); + } + fullSpec = TRUE; + } + } + + if (path == NULL) { + /* + * If no path was given, the user wants one of the pre-defined shells, + * yes? So we find the one s/he wants with the help of JobMatchShell + * and set things up the right way. shellPath will be set up by + * Shell_Init. + */ + if (newShell.name == NULL) { + Parse_Error(PARSE_FATAL, "Neither path nor name specified"); + free(words); + return(FAILURE); + } else { + if ((sh = JobMatchShell(newShell.name)) == NULL) { + Parse_Error(PARSE_WARNING, "%s: No matching shell", + newShell.name); + free(words); + return(FAILURE); + } + commandShell = sh; + shellName = newShell.name; + if (shellPath) { + /* Shell_Init has already been called! Do it again. */ + free(UNCONST(shellPath)); + shellPath = NULL; + Shell_Init(); + } + } + } else { + /* + * The user provided a path. If s/he gave nothing else (fullSpec is + * FALSE), try and find a matching shell in the ones we know of. + * Else we just take the specification at its word and copy it + * to a new location. In either case, we need to record the + * path the user gave for the shell. + */ + shellPath = path; + path = strrchr(path, '/'); + if (path == NULL) { + path = UNCONST(shellPath); + } else { + path += 1; + } + if (newShell.name != NULL) { + shellName = newShell.name; + } else { + shellName = path; + } + if (!fullSpec) { + if ((sh = JobMatchShell(shellName)) == NULL) { + Parse_Error(PARSE_WARNING, "%s: No matching shell", + shellName); + free(words); + return(FAILURE); + } + commandShell = sh; + } else { + commandShell = bmake_malloc(sizeof(Shell)); + *commandShell = newShell; + } + /* this will take care of shellErrFlag */ + Shell_Init(); + } + + if (commandShell->echoOn && commandShell->echoOff) { + commandShell->hasEchoCtl = TRUE; + } + + if (!commandShell->hasErrCtl) { + if (commandShell->errCheck == NULL) { + commandShell->errCheck = ""; + } + if (commandShell->ignErr == NULL) { + commandShell->ignErr = "%s\n"; + } + } + + /* + * Do not free up the words themselves, since they might be in use by the + * shell specification. + */ + free(words); + return SUCCESS; +} + +/*- + *----------------------------------------------------------------------- + * JobInterrupt -- + * Handle the receipt of an interrupt. + * + * Input: + * runINTERRUPT Non-zero if commands for the .INTERRUPT target + * should be executed + * signo signal received + * + * Results: + * None + * + * Side Effects: + * All children are killed. Another job will be started if the + * .INTERRUPT target was given. + *----------------------------------------------------------------------- + */ +static void +JobInterrupt(int runINTERRUPT, int signo) +{ + Job *job; /* job descriptor in that element */ + GNode *interrupt; /* the node describing the .INTERRUPT target */ + sigset_t mask; + GNode *gn; + + aborting = ABORT_INTERRUPT; + + JobSigLock(&mask); + + for (job = job_table; job < job_table_end; job++) { + if (job->job_state != JOB_ST_RUNNING) + continue; + + gn = job->node; + + JobDeleteTarget(gn); + if (job->pid) { + if (DEBUG(JOB)) { + (void)fprintf(debug_file, + "JobInterrupt passing signal %d to child %d.\n", + signo, job->pid); + } + KILLPG(job->pid, signo); + } + } + + JobSigUnlock(&mask); + + if (runINTERRUPT && !touchFlag) { + interrupt = Targ_FindNode(".INTERRUPT", TARG_NOCREATE); + if (interrupt != NULL) { + ignoreErrors = FALSE; + JobRun(interrupt); + } + } + Trace_Log(MAKEINTR, 0); + exit(signo); +} + +/* + *----------------------------------------------------------------------- + * Job_Finish -- + * Do final processing such as the running of the commands + * attached to the .END target. + * + * Results: + * Number of errors reported. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +int +Job_Finish(void) +{ + if (postCommands != NULL && + (!Lst_IsEmpty(postCommands->commands) || + !Lst_IsEmpty(postCommands->children))) { + if (errors) { + Error("Errors reported so .END ignored"); + } else { + JobRun(postCommands); + } + } + return(errors); +} + +/*- + *----------------------------------------------------------------------- + * Job_End -- + * Cleanup any memory used by the jobs module + * + * Results: + * None. + * + * Side Effects: + * Memory is freed + *----------------------------------------------------------------------- + */ +void +Job_End(void) +{ +#ifdef CLEANUP + free(shellArgv); +#endif +} + +/*- + *----------------------------------------------------------------------- + * Job_Wait -- + * Waits for all running jobs to finish and returns. Sets 'aborting' + * to ABORT_WAIT to prevent other jobs from starting. + * + * Results: + * None. + * + * Side Effects: + * Currently running jobs finish. + * + *----------------------------------------------------------------------- + */ +void +Job_Wait(void) +{ + aborting = ABORT_WAIT; + while (jobTokensRunning != 0) { + Job_CatchOutput(); + } + aborting = 0; +} + +/*- + *----------------------------------------------------------------------- + * Job_AbortAll -- + * Abort all currently running jobs without handling output or anything. + * This function is to be called only in the event of a major + * error. Most definitely NOT to be called from JobInterrupt. + * + * Results: + * None + * + * Side Effects: + * All children are killed, not just the firstborn + *----------------------------------------------------------------------- + */ +void +Job_AbortAll(void) +{ + Job *job; /* the job descriptor in that element */ + int foo; + + aborting = ABORT_ERROR; + + if (jobTokensRunning) { + for (job = job_table; job < job_table_end; job++) { + if (job->job_state != JOB_ST_RUNNING) + continue; + /* + * kill the child process with increasingly drastic signals to make + * darn sure it's dead. + */ + KILLPG(job->pid, SIGINT); + KILLPG(job->pid, SIGKILL); + } + } + + /* + * Catch as many children as want to report in at first, then give up + */ + while (waitpid((pid_t) -1, &foo, WNOHANG) > 0) + continue; +} + + +/*- + *----------------------------------------------------------------------- + * JobRestartJobs -- + * Tries to restart stopped jobs if there are slots available. + * Called in process context in response to a SIGCONT. + * + * Results: + * None. + * + * Side Effects: + * Resumes jobs. + * + *----------------------------------------------------------------------- + */ +static void +JobRestartJobs(void) +{ + Job *job; + + for (job = job_table; job < job_table_end; job++) { + if (job->job_state == JOB_ST_RUNNING && + (make_suspended || job->job_suspended)) { + if (DEBUG(JOB)) { + (void)fprintf(debug_file, "Restarting stopped job pid %d.\n", + job->pid); + } + if (job->job_suspended) { + (void)printf("*** [%s] Continued\n", job->node->name); + (void)fflush(stdout); + } + job->job_suspended = 0; + if (KILLPG(job->pid, SIGCONT) != 0 && DEBUG(JOB)) { + fprintf(debug_file, "Failed to send SIGCONT to %d\n", job->pid); + } + } + if (job->job_state == JOB_ST_FINISHED) + /* Job exit deferred after calling waitpid() in a signal handler */ + JobFinish(job, job->exit_status); + } + make_suspended = 0; +} + +static void +watchfd(Job *job) +{ + if (job->inPollfd != NULL) + Punt("Watching watched job"); + + fds[nfds].fd = job->inPipe; + fds[nfds].events = POLLIN; + jobfds[nfds] = job; + job->inPollfd = &fds[nfds]; + nfds++; +} + +static void +clearfd(Job *job) +{ + int i; + if (job->inPollfd == NULL) + Punt("Unwatching unwatched job"); + i = job->inPollfd - fds; + nfds--; + /* + * Move last job in table into hole made by dead job. + */ + if (nfds != i) { + fds[i] = fds[nfds]; + jobfds[i] = jobfds[nfds]; + jobfds[i]->inPollfd = &fds[i]; + } + job->inPollfd = NULL; +} + +static int +readyfd(Job *job) +{ + if (job->inPollfd == NULL) + Punt("Polling unwatched job"); + return (job->inPollfd->revents & POLLIN) != 0; +} + +/*- + *----------------------------------------------------------------------- + * JobTokenAdd -- + * Put a token into the job pipe so that some make process can start + * another job. + * + * Side Effects: + * Allows more build jobs to be spawned somewhere. + * + *----------------------------------------------------------------------- + */ + +static void +JobTokenAdd(void) +{ + char tok = JOB_TOKENS[aborting], tok1; + + /* If we are depositing an error token flush everything else */ + while (tok != '+' && read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; + + if (DEBUG(JOB)) + fprintf(debug_file, "(%d) aborting %d, deposit token %c\n", + getpid(), aborting, JOB_TOKENS[aborting]); + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) + continue; +} + +/*- + *----------------------------------------------------------------------- + * Job_ServerStartTokenAdd -- + * Prep the job token pipe in the root make process. + * + *----------------------------------------------------------------------- + */ + +void +Job_ServerStart(int max_tokens, int jp_0, int jp_1) +{ + int i; + char jobarg[64]; + + if (jp_0 >= 0 && jp_1 >= 0) { + /* Pipe passed in from parent */ + tokenWaitJob.inPipe = jp_0; + tokenWaitJob.outPipe = jp_1; + (void)fcntl(jp_0, F_SETFD, FD_CLOEXEC); + (void)fcntl(jp_1, F_SETFD, FD_CLOEXEC); + return; + } + + JobCreatePipe(&tokenWaitJob, 15); + + snprintf(jobarg, sizeof(jobarg), "%d,%d", + tokenWaitJob.inPipe, tokenWaitJob.outPipe); + + Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); + Var_Append(MAKEFLAGS, jobarg, VAR_GLOBAL); + + /* + * Preload the job pipe with one token per job, save the one + * "extra" token for the primary job. + * + * XXX should clip maxJobs against PIPE_BUF -- if max_tokens is + * larger than the write buffer size of the pipe, we will + * deadlock here. + */ + for (i = 1; i < max_tokens; i++) + JobTokenAdd(); +} + +/*- + *----------------------------------------------------------------------- + * Job_TokenReturn -- + * Return a withdrawn token to the pool. + * + *----------------------------------------------------------------------- + */ + +void +Job_TokenReturn(void) +{ + jobTokensRunning--; + if (jobTokensRunning < 0) + Punt("token botch"); + if (jobTokensRunning || JOB_TOKENS[aborting] != '+') + JobTokenAdd(); +} + +/*- + *----------------------------------------------------------------------- + * Job_TokenWithdraw -- + * Attempt to withdraw a token from the pool. + * + * Results: + * Returns TRUE if a token was withdrawn, and FALSE if the pool + * is currently empty. + * + * Side Effects: + * If pool is empty, set wantToken so that we wake up + * when a token is released. + * + *----------------------------------------------------------------------- + */ + + +Boolean +Job_TokenWithdraw(void) +{ + char tok, tok1; + int count; + + wantToken = 0; + if (DEBUG(JOB)) + fprintf(debug_file, "Job_TokenWithdraw(%d): aborting %d, running %d\n", + getpid(), aborting, jobTokensRunning); + + if (aborting || (jobTokensRunning >= maxJobs)) + return FALSE; + + count = read(tokenWaitJob.inPipe, &tok, 1); + if (count == 0) + Fatal("eof on job pipe!"); + if (count < 0 && jobTokensRunning != 0) { + if (errno != EAGAIN) { + Fatal("job pipe read: %s", strerror(errno)); + } + if (DEBUG(JOB)) + fprintf(debug_file, "(%d) blocked for token\n", getpid()); + return FALSE; + } + + if (count == 1 && tok != '+') { + /* make being abvorted - remove any other job tokens */ + if (DEBUG(JOB)) + fprintf(debug_file, "(%d) aborted by token %c\n", getpid(), tok); + while (read(tokenWaitJob.inPipe, &tok1, 1) == 1) + continue; + /* And put the stopper back */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) + continue; + Fatal("A failure has been detected in another branch of the parallel make"); + } + + if (count == 1 && jobTokensRunning == 0) + /* We didn't want the token really */ + while (write(tokenWaitJob.outPipe, &tok, 1) == -1 && errno == EAGAIN) + continue; + + jobTokensRunning++; + if (DEBUG(JOB)) + fprintf(debug_file, "(%d) withdrew token\n", getpid()); + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * Job_RunTarget -- + * Run the named target if found. If a filename is specified, then + * set that to the sources. + * + * Results: + * None + * + * Side Effects: + * exits if the target fails. + * + *----------------------------------------------------------------------- + */ +Boolean +Job_RunTarget(const char *target, const char *fname) { + GNode *gn = Targ_FindNode(target, TARG_NOCREATE); + + if (gn == NULL) + return FALSE; + + if (fname) + Var_Set(ALLSRC, fname, gn, 0); + + JobRun(gn); + if (gn->made == ERROR) { + PrintOnError(gn, "\n\nStop."); + exit(1); + } + return TRUE; +} + +#ifdef USE_SELECT +int +emul_poll(struct pollfd *fd, int nfd, int timeout) +{ + fd_set rfds, wfds; + int i, maxfd, nselect, npoll; + struct timeval tv, *tvp; + long usecs; + + FD_ZERO(&rfds); + FD_ZERO(&wfds); + + maxfd = -1; + for (i = 0; i < nfd; i++) { + fd[i].revents = 0; + + if (fd[i].events & POLLIN) + FD_SET(fd[i].fd, &rfds); + + if (fd[i].events & POLLOUT) + FD_SET(fd[i].fd, &wfds); + + if (fd[i].fd > maxfd) + maxfd = fd[i].fd; + } + + if (maxfd >= FD_SETSIZE) { + Punt("Ran out of fd_set slots; " + "recompile with a larger FD_SETSIZE."); + } + + if (timeout < 0) { + tvp = NULL; + } else { + usecs = timeout * 1000; + tv.tv_sec = usecs / 1000000; + tv.tv_usec = usecs % 1000000; + tvp = &tv; + } + + nselect = select(maxfd + 1, &rfds, &wfds, 0, tvp); + + if (nselect <= 0) + return nselect; + + npoll = 0; + for (i = 0; i < nfd; i++) { + if (FD_ISSET(fd[i].fd, &rfds)) + fd[i].revents |= POLLIN; + + if (FD_ISSET(fd[i].fd, &wfds)) + fd[i].revents |= POLLOUT; + + if (fd[i].revents) + npoll++; + } + + return npoll; +} +#endif /* USE_SELECT */ diff --git a/usr.bin/make/job.h b/usr.bin/make/job.h new file mode 100644 index 0000000..91e2c87 --- /dev/null +++ b/usr.bin/make/job.h @@ -0,0 +1,274 @@ +/* $NetBSD: job.h,v 1.42 2013/07/05 22:14:56 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)job.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)job.h 8.1 (Berkeley) 6/6/93 + */ + +/*- + * job.h -- + * Definitions pertaining to the running of jobs in parallel mode. + */ +#ifndef _JOB_H_ +#define _JOB_H_ + +#define TMPPAT "makeXXXXXX" /* relative to tmpdir */ + +#ifdef USE_SELECT +/* + * Emulate poll() in terms of select(). This is not a complete + * emulation but it is sufficient for make's purposes. + */ + +#define poll emul_poll +#define pollfd emul_pollfd + +struct emul_pollfd { + int fd; + short events; + short revents; +}; + +#define POLLIN 0x0001 +#define POLLOUT 0x0004 + +int +emul_poll(struct pollfd *fd, int nfd, int timeout); +#endif + +/* + * The POLL_MSEC constant determines the maximum number of milliseconds spent + * in poll before coming out to see if a child has finished. + */ +#define POLL_MSEC 5000 + + +/*- + * Job Table definitions. + * + * Each job has several things associated with it: + * 1) The process id of the child shell + * 2) The graph node describing the target being made by this job + * 3) A LstNode for the first command to be saved after the job + * completes. This is NULL if there was no "..." in the job's + * commands. + * 4) An FILE* for writing out the commands. This is only + * used before the job is actually started. + * 5) The output is being caught via a pipe and + * the descriptors of our pipe, an array in which output is line + * buffered and the current position in that buffer are all + * maintained for each job. + * 6) A word of flags which determine how the module handles errors, + * echoing, etc. for the job + * + * When a job is finished, the Make_Update function is called on each of the + * parents of the node which was just remade. This takes care of the upward + * traversal of the dependency graph. + */ +struct pollfd; + + +#ifdef USE_META +# include "meta.h" +#endif + +#define JOB_BUFSIZE 1024 +typedef struct Job { + int pid; /* The child's process ID */ + GNode *node; /* The target the child is making */ + LstNode tailCmds; /* The node of the first command to be + * saved when the job has been run */ + FILE *cmdFILE; /* When creating the shell script, this is + * where the commands go */ + int exit_status; /* from wait4() in signal handler */ + char job_state; /* status of the job entry */ +#define JOB_ST_FREE 0 /* Job is available */ +#define JOB_ST_SETUP 1 /* Job is allocated but otherwise invalid */ +#define JOB_ST_RUNNING 3 /* Job is running, pid valid */ +#define JOB_ST_FINISHED 4 /* Job is done (ie after SIGCHILD) */ + char job_suspended; + short flags; /* Flags to control treatment of job */ +#define JOB_IGNERR 0x001 /* Ignore non-zero exits */ +#define JOB_SILENT 0x002 /* no output */ +#define JOB_SPECIAL 0x004 /* Target is a special one. i.e. run it locally + * if we can't export it and maxLocal is 0 */ +#define JOB_IGNDOTS 0x008 /* Ignore "..." lines when processing + * commands */ +#define JOB_TRACED 0x400 /* we've sent 'set -x' */ + + int jobPipe[2]; /* Pipe for readind output from job */ + struct pollfd *inPollfd; /* pollfd associated with inPipe */ + char outBuf[JOB_BUFSIZE + 1]; + /* Buffer for storing the output of the + * job, line by line */ + int curPos; /* Current position in op_outBuf */ + +#ifdef USE_META + struct BuildMon bm; +#endif +} Job; + +#define inPipe jobPipe[0] +#define outPipe jobPipe[1] + + +/*- + * Shell Specifications: + * Each shell type has associated with it the following information: + * 1) The string which must match the last character of the shell name + * for the shell to be considered of this type. The longest match + * wins. + * 2) A command to issue to turn off echoing of command lines + * 3) A command to issue to turn echoing back on again + * 4) What the shell prints, and its length, when given the echo-off + * command. This line will not be printed when received from the shell + * 5) A boolean to tell if the shell has the ability to control + * error checking for individual commands. + * 6) The string to turn this checking on. + * 7) The string to turn it off. + * 8) The command-flag to give to cause the shell to start echoing + * commands right away. + * 9) The command-flag to cause the shell to Lib_Exit when an error is + * detected in one of the commands. + * + * Some special stuff goes on if a shell doesn't have error control. In such + * a case, errCheck becomes a printf template for echoing the command, + * should echoing be on and ignErr becomes another printf template for + * executing the command while ignoring the return status. Finally errOut + * is a printf template for running the command and causing the shell to + * exit on error. If any of these strings are empty when hasErrCtl is FALSE, + * the command will be executed anyway as is and if it causes an error, so be + * it. Any templates setup to echo the command will escape any '$ ` \ "'i + * characters in the command string to avoid common problems with + * echo "%s\n" as a template. + */ +typedef struct Shell { + const char *name; /* the name of the shell. For Bourne and C + * shells, this is used only to find the + * shell description when used as the single + * source of a .SHELL target. For user-defined + * shells, this is the full path of the shell. + */ + Boolean hasEchoCtl; /* True if both echoOff and echoOn defined */ + const char *echoOff; /* command to turn off echo */ + const char *echoOn; /* command to turn it back on again */ + const char *noPrint; /* command to skip when printing output from + * shell. This is usually the command which + * was executed to turn off echoing */ + int noPLen; /* length of noPrint command */ + Boolean hasErrCtl; /* set if can control error checking for + * individual commands */ + const char *errCheck; /* string to turn error checking on */ + const char *ignErr; /* string to turn off error checking */ + const char *errOut; /* string to use for testing exit code */ + const char *newline; /* string literal that results in a newline + * character when it appears outside of any + * 'quote' or "quote" characters */ + char commentChar; /* character used by shell for comment lines */ + + /* + * command-line flags + */ + const char *echo; /* echo commands */ + const char *exit; /* exit on error */ +} Shell; + +extern const char *shellPath; +extern const char *shellName; +extern char *shellErrFlag; + +extern int jobTokensRunning; /* tokens currently "out" */ +extern int maxJobs; /* Max jobs we can run */ + +void Shell_Init(void); +const char *Shell_GetNewline(void); +void Job_Touch(GNode *, Boolean); +Boolean Job_CheckCommands(GNode *, void (*abortProc )(const char *, ...)); +#define CATCH_BLOCK 1 +void Job_CatchChildren(void); +void Job_CatchOutput(void); +void Job_Make(GNode *); +void Job_Init(void); +Boolean Job_Full(void); +Boolean Job_Empty(void); +ReturnStatus Job_ParseShell(char *); +int Job_Finish(void); +void Job_End(void); +void Job_Wait(void); +void Job_AbortAll(void); +void JobFlagForMigration(int); +void Job_TokenReturn(void); +Boolean Job_TokenWithdraw(void); +void Job_ServerStart(int, int, int); +void Job_SetPrefix(void); +Boolean Job_RunTarget(const char *, const char *); + +#endif /* _JOB_H_ */ diff --git a/usr.bin/make/lst.h b/usr.bin/make/lst.h new file mode 100644 index 0000000..e207bc8 --- /dev/null +++ b/usr.bin/make/lst.h @@ -0,0 +1,189 @@ +/* $NetBSD: lst.h,v 1.20 2014/09/07 20:55:34 joerg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990 The Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)lst.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1988, 1989 by Adam de Boor + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)lst.h 8.1 (Berkeley) 6/6/93 + */ + +/*- + * lst.h -- + * Header for using the list library + */ +#ifndef _LST_H_ +#define _LST_H_ + +#include +#include + +#include "sprite.h" + +/* + * basic typedef. This is what the Lst_ functions handle + */ + +typedef struct List *Lst; +typedef struct ListNode *LstNode; + +typedef void *DuplicateProc(void *); +typedef void FreeProc(void *); + +#define LST_CONCNEW 0 /* create new LstNode's when using Lst_Concat */ +#define LST_CONCLINK 1 /* relink LstNode's when using Lst_Concat */ + +/* + * Creation/destruction functions + */ +/* Create a new list */ +Lst Lst_Init(Boolean); +/* Duplicate an existing list */ +Lst Lst_Duplicate(Lst, DuplicateProc *); +/* Destroy an old one */ +void Lst_Destroy(Lst, FreeProc *); +/* True if list is empty */ +Boolean Lst_IsEmpty(Lst); + +/* + * Functions to modify a list + */ +/* Insert an element before another */ +ReturnStatus Lst_InsertBefore(Lst, LstNode, void *); +/* Insert an element after another */ +ReturnStatus Lst_InsertAfter(Lst, LstNode, void *); +/* Place an element at the front of a lst. */ +ReturnStatus Lst_AtFront(Lst, void *); +/* Place an element at the end of a lst. */ +ReturnStatus Lst_AtEnd(Lst, void *); +/* Remove an element */ +ReturnStatus Lst_Remove(Lst, LstNode); +/* Replace a node with a new value */ +ReturnStatus Lst_Replace(LstNode, void *); +/* Concatenate two lists */ +ReturnStatus Lst_Concat(Lst, Lst, int); + +/* + * Node-specific functions + */ +/* Return first element in list */ +LstNode Lst_First(Lst); +/* Return last element in list */ +LstNode Lst_Last(Lst); +/* Return successor to given element */ +LstNode Lst_Succ(LstNode); +/* Return predecessor to given element */ +LstNode Lst_Prev(LstNode); +/* Get datum from LstNode */ +void *Lst_Datum(LstNode); + +/* + * Functions for entire lists + */ +/* Find an element in a list */ +LstNode Lst_Find(Lst, const void *, int (*)(const void *, const void *)); +/* Find an element starting from somewhere */ +LstNode Lst_FindFrom(Lst, LstNode, const void *, + int (*cProc)(const void *, const void *)); +/* + * See if the given datum is on the list. Returns the LstNode containing + * the datum + */ +LstNode Lst_Member(Lst, void *); +/* Apply a function to all elements of a lst */ +int Lst_ForEach(Lst, int (*)(void *, void *), void *); +/* + * Apply a function to all elements of a lst starting from a certain point. + * If the list is circular, the application will wrap around to the + * beginning of the list again. + */ +int Lst_ForEachFrom(Lst, LstNode, int (*)(void *, void *), + void *); +/* + * these functions are for dealing with a list as a table, of sorts. + * An idea of the "current element" is kept and used by all the functions + * between Lst_Open() and Lst_Close(). + */ +/* Open the list */ +ReturnStatus Lst_Open(Lst); +/* Next element please */ +LstNode Lst_Next(Lst); +/* Done yet? */ +Boolean Lst_IsAtEnd(Lst); +/* Finish table access */ +void Lst_Close(Lst); + +/* + * for using the list as a queue + */ +/* Place an element at tail of queue */ +ReturnStatus Lst_EnQueue(Lst, void *); +/* Remove an element from head of queue */ +void *Lst_DeQueue(Lst); + +#endif /* _LST_H_ */ diff --git a/usr.bin/make/lst.lib/Makefile b/usr.bin/make/lst.lib/Makefile new file mode 100644 index 0000000..5b33a50 --- /dev/null +++ b/usr.bin/make/lst.lib/Makefile @@ -0,0 +1,10 @@ +# $NetBSD: Makefile,v 1.6 2006/11/11 21:23:36 dsl Exp $ + +OBJ=lstAppend.o lstDupl.o lstInit.o lstOpen.o lstAtEnd.o lstEnQueue.o \ + lstInsert.o lstAtFront.o lstIsAtEnd.o lstClose.o lstFind.o lstIsEmpty.o \ + lstRemove.o lstConcat.o lstFindFrom.o lstLast.o lstReplace.o lstFirst.o \ + lstDatum.o lstForEach.o lstMember.o lstSucc.o lstDeQueue.o \ + lstForEachFrom.o lstDestroy.o lstNext.o lstPrev.o + +CPPFLAGS=-I${.CURDIR}/.. +all: ${OBJ} diff --git a/usr.bin/make/lst.lib/lstAppend.c b/usr.bin/make/lst.lib/lstAppend.c new file mode 100644 index 0000000..4dafe83 --- /dev/null +++ b/usr.bin/make/lst.lib/lstAppend.c @@ -0,0 +1,122 @@ +/* $NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstAppend.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstAppend.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstAppend.c -- + * Add a new node with a new datum after an existing node + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_InsertAfter -- + * Create a new node and add it to the given list after the given node. + * + * Input: + * l affected list + * ln node after which to append the datum + * d said datum + * + * Results: + * SUCCESS if all went well. + * + * Side Effects: + * A new ListNode is created and linked in to the List. The lastPtr + * field of the List will be altered if ln is the last node in the + * list. lastPtr and firstPtr will alter if the list was empty and + * ln was NULL. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_InsertAfter(Lst l, LstNode ln, void *d) +{ + List list; + ListNode lNode; + ListNode nLNode; + + if (LstValid (l) && (ln == NULL && LstIsEmpty (l))) { + goto ok; + } + + if (!LstValid (l) || LstIsEmpty (l) || ! LstNodeValid (ln, l)) { + return (FAILURE); + } + ok: + + list = l; + lNode = ln; + + PAlloc (nLNode, ListNode); + nLNode->datum = d; + nLNode->useCount = nLNode->flags = 0; + + if (lNode == NULL) { + if (list->isCirc) { + nLNode->nextPtr = nLNode->prevPtr = nLNode; + } else { + nLNode->nextPtr = nLNode->prevPtr = NULL; + } + list->firstPtr = list->lastPtr = nLNode; + } else { + nLNode->prevPtr = lNode; + nLNode->nextPtr = lNode->nextPtr; + + lNode->nextPtr = nLNode; + if (nLNode->nextPtr != NULL) { + nLNode->nextPtr->prevPtr = nLNode; + } + + if (lNode == list->lastPtr) { + list->lastPtr = nLNode; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstAtEnd.c b/usr.bin/make/lst.lib/lstAtEnd.c new file mode 100644 index 0000000..10f191a --- /dev/null +++ b/usr.bin/make/lst.lib/lstAtEnd.c @@ -0,0 +1,79 @@ +/* $NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstAtEnd.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstAtEnd.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstAtEnd.c -- + * Add a node at the end of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_AtEnd -- + * Add a node to the end of the given list + * + * Input: + * l List to which to add the datum + * d Datum to add + * + * Results: + * SUCCESS if life is good. + * + * Side Effects: + * A new ListNode is created and added to the list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_AtEnd(Lst l, void *d) +{ + LstNode end; + + end = Lst_Last(l); + return (Lst_InsertAfter(l, end, d)); +} diff --git a/usr.bin/make/lst.lib/lstAtFront.c b/usr.bin/make/lst.lib/lstAtFront.c new file mode 100644 index 0000000..d8be166 --- /dev/null +++ b/usr.bin/make/lst.lib/lstAtFront.c @@ -0,0 +1,76 @@ +/* $NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstAtFront.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstAtFront.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstAtFront.c -- + * Add a node at the front of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_AtFront -- + * Place a piece of data at the front of a list + * + * Results: + * SUCCESS or FAILURE + * + * Side Effects: + * A new ListNode is created and stuck at the front of the list. + * hence, firstPtr (and possible lastPtr) in the list are altered. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_AtFront(Lst l, void *d) +{ + LstNode front; + + front = Lst_First(l); + return (Lst_InsertBefore(l, front, d)); +} diff --git a/usr.bin/make/lst.lib/lstClose.c b/usr.bin/make/lst.lib/lstClose.c new file mode 100644 index 0000000..06b68c5 --- /dev/null +++ b/usr.bin/make/lst.lib/lstClose.c @@ -0,0 +1,86 @@ +/* $NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstClose.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstClose.c,v 1.11 2006/10/27 21:37:25 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstClose.c -- + * Close a list for sequential access. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Close -- + * Close a list which was opened for sequential access. + * + * Input: + * l The list to close + * + * Results: + * None. + * + * Side Effects: + * The list is closed. + * + *----------------------------------------------------------------------- + */ +void +Lst_Close(Lst l) +{ + List list = l; + + if (LstValid(l) == TRUE) { + list->isOpen = FALSE; + list->atEnd = Unknown; + } +} + diff --git a/usr.bin/make/lst.lib/lstConcat.c b/usr.bin/make/lst.lib/lstConcat.c new file mode 100644 index 0000000..534d34e --- /dev/null +++ b/usr.bin/make/lst.lib/lstConcat.c @@ -0,0 +1,185 @@ +/* $NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstConcat.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstConcat.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * listConcat.c -- + * Function to concatentate two lists. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Concat -- + * Concatenate two lists. New elements are created to hold the data + * elements, if specified, but the elements themselves are not copied. + * If the elements should be duplicated to avoid confusion with another + * list, the Lst_Duplicate function should be called first. + * If LST_CONCLINK is specified, the second list is destroyed since + * its pointers have been corrupted and the list is no longer useable. + * + * Input: + * l1 The list to which l2 is to be appended + * l2 The list to append to l1 + * flags LST_CONCNEW if LstNode's should be duplicated + * LST_CONCLINK if should just be relinked + * + * Results: + * SUCCESS if all went well. FAILURE otherwise. + * + * Side Effects: + * New elements are created and appended the first list. + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Concat(Lst l1, Lst l2, int flags) +{ + ListNode ln; /* original LstNode */ + ListNode nln; /* new LstNode */ + ListNode last; /* the last element in the list. Keeps + * bookkeeping until the end */ + List list1 = l1; + List list2 = l2; + + if (!LstValid (l1) || !LstValid (l2)) { + return (FAILURE); + } + + if (flags == LST_CONCLINK) { + if (list2->firstPtr != NULL) { + /* + * We set the nextPtr of the + * last element of list two to be NIL to make the loop easier and + * so we don't need an extra case should the first list turn + * out to be non-circular -- the final element will already point + * to NIL space and the first element will be untouched if it + * existed before and will also point to NIL space if it didn't. + */ + list2->lastPtr->nextPtr = NULL; + /* + * So long as the second list isn't empty, we just link the + * first element of the second list to the last element of the + * first list. If the first list isn't empty, we then link the + * last element of the list to the first element of the second list + * The last element of the second list, if it exists, then becomes + * the last element of the first list. + */ + list2->firstPtr->prevPtr = list1->lastPtr; + if (list1->lastPtr != NULL) { + list1->lastPtr->nextPtr = list2->firstPtr; + } else { + list1->firstPtr = list2->firstPtr; + } + list1->lastPtr = list2->lastPtr; + } + if (list1->isCirc && list1->firstPtr != NULL) { + /* + * If the first list is supposed to be circular and it is (now) + * non-empty, we must make sure it's circular by linking the + * first element to the last and vice versa + */ + list1->firstPtr->prevPtr = list1->lastPtr; + list1->lastPtr->nextPtr = list1->firstPtr; + } + free(l2); + } else if (list2->firstPtr != NULL) { + /* + * We set the nextPtr of the last element of list 2 to be nil to make + * the loop less difficult. The loop simply goes through the entire + * second list creating new LstNodes and filling in the nextPtr, and + * prevPtr to fit into l1 and its datum field from the + * datum field of the corresponding element in l2. The 'last' node + * follows the last of the new nodes along until the entire l2 has + * been appended. Only then does the bookkeeping catch up with the + * changes. During the first iteration of the loop, if 'last' is nil, + * the first list must have been empty so the newly-created node is + * made the first node of the list. + */ + list2->lastPtr->nextPtr = NULL; + for (last = list1->lastPtr, ln = list2->firstPtr; + ln != NULL; + ln = ln->nextPtr) + { + PAlloc (nln, ListNode); + nln->datum = ln->datum; + if (last != NULL) { + last->nextPtr = nln; + } else { + list1->firstPtr = nln; + } + nln->prevPtr = last; + nln->flags = nln->useCount = 0; + last = nln; + } + + /* + * Finish bookkeeping. The last new element becomes the last element + * of list one. + */ + list1->lastPtr = last; + + /* + * The circularity of both list one and list two must be corrected + * for -- list one because of the new nodes added to it; list two + * because of the alteration of list2->lastPtr's nextPtr to ease the + * above for loop. + */ + if (list1->isCirc) { + list1->lastPtr->nextPtr = list1->firstPtr; + list1->firstPtr->prevPtr = list1->lastPtr; + } else { + last->nextPtr = NULL; + } + + if (list2->isCirc) { + list2->lastPtr->nextPtr = list2->firstPtr; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstDatum.c b/usr.bin/make/lst.lib/lstDatum.c new file mode 100644 index 0000000..6e2d9ad --- /dev/null +++ b/usr.bin/make/lst.lib/lstDatum.c @@ -0,0 +1,77 @@ +/* $NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstDatum.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstDatum.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstDatum.c -- + * Return the datum associated with a list node. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Datum -- + * Return the datum stored in the given node. + * + * Results: + * The datum or NULL if the node is invalid. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +void * +Lst_Datum(LstNode ln) +{ + if (ln != NULL) { + return ((ln)->datum); + } else { + return NULL; + } +} + diff --git a/usr.bin/make/lst.lib/lstDeQueue.c b/usr.bin/make/lst.lib/lstDeQueue.c new file mode 100644 index 0000000..bdb05cc --- /dev/null +++ b/usr.bin/make/lst.lib/lstDeQueue.c @@ -0,0 +1,87 @@ +/* $NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstDeQueue.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstDeQueue.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstDeQueue.c -- + * Remove the node and return its datum from the head of the list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_DeQueue -- + * Remove and return the datum at the head of the given list. + * + * Results: + * The datum in the node at the head or NULL if the list + * is empty. + * + * Side Effects: + * The head node is removed from the list. + * + *----------------------------------------------------------------------- + */ +void * +Lst_DeQueue(Lst l) +{ + void *rd; + ListNode tln; + + tln = Lst_First(l); + if (tln == NULL) { + return NULL; + } + + rd = tln->datum; + if (Lst_Remove(l, tln) == FAILURE) { + return NULL; + } else { + return (rd); + } +} + diff --git a/usr.bin/make/lst.lib/lstDestroy.c b/usr.bin/make/lst.lib/lstDestroy.c new file mode 100644 index 0000000..92c5b2b --- /dev/null +++ b/usr.bin/make/lst.lib/lstDestroy.c @@ -0,0 +1,101 @@ +/* $NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstDestroy.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstDestroy.c,v 1.16 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstDestroy.c -- + * Nuke a list and all its resources + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Destroy -- + * Destroy a list and free all its resources. If the freeProc is + * given, it is called with the datum from each node in turn before + * the node is freed. + * + * Results: + * None. + * + * Side Effects: + * The given list is freed in its entirety. + * + *----------------------------------------------------------------------- + */ +void +Lst_Destroy(Lst list, FreeProc *freeProc) +{ + ListNode ln; + ListNode tln = NULL; + + if (list == NULL) + return; + + /* To ease scanning */ + if (list->lastPtr != NULL) + list->lastPtr->nextPtr = NULL; + else { + free(list); + return; + } + + if (freeProc) { + for (ln = list->firstPtr; ln != NULL; ln = tln) { + tln = ln->nextPtr; + freeProc(ln->datum); + free(ln); + } + } else { + for (ln = list->firstPtr; ln != NULL; ln = tln) { + tln = ln->nextPtr; + free(ln); + } + } + + free(list); +} diff --git a/usr.bin/make/lst.lib/lstDupl.c b/usr.bin/make/lst.lib/lstDupl.c new file mode 100644 index 0000000..2174ff7 --- /dev/null +++ b/usr.bin/make/lst.lib/lstDupl.c @@ -0,0 +1,107 @@ +/* $NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstDupl.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstDupl.c,v 1.16 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * listDupl.c -- + * Duplicate a list. This includes duplicating the individual + * elements. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Duplicate -- + * Duplicate an entire list. If a function to copy a void *is + * given, the individual client elements will be duplicated as well. + * + * Input: + * l the list to duplicate + * copyProc A function to duplicate each void * + * + * Results: + * The new Lst structure or NULL if failure. + * + * Side Effects: + * A new list is created. + *----------------------------------------------------------------------- + */ +Lst +Lst_Duplicate(Lst l, DuplicateProc *copyProc) +{ + Lst nl; + ListNode ln; + List list = l; + + if (!LstValid (l)) { + return NULL; + } + + nl = Lst_Init(list->isCirc); + if (nl == NULL) { + return NULL; + } + + ln = list->firstPtr; + while (ln != NULL) { + if (copyProc != NULL) { + if (Lst_AtEnd(nl, copyProc(ln->datum)) == FAILURE) { + return NULL; + } + } else if (Lst_AtEnd(nl, ln->datum) == FAILURE) { + return NULL; + } + + if (list->isCirc && ln == list->lastPtr) { + ln = NULL; + } else { + ln = ln->nextPtr; + } + } + + return (nl); +} diff --git a/usr.bin/make/lst.lib/lstEnQueue.c b/usr.bin/make/lst.lib/lstEnQueue.c new file mode 100644 index 0000000..be386c9 --- /dev/null +++ b/usr.bin/make/lst.lib/lstEnQueue.c @@ -0,0 +1,78 @@ +/* $NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstEnQueue.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstEnQueue.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstEnQueue.c-- + * Treat the list as a queue and place a datum at its end + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_EnQueue -- + * Add the datum to the tail of the given list. + * + * Results: + * SUCCESS or FAILURE as returned by Lst_InsertAfter. + * + * Side Effects: + * the lastPtr field is altered all the time and the firstPtr field + * will be altered if the list used to be empty. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_EnQueue(Lst l, void *d) +{ + if (LstValid (l) == FALSE) { + return (FAILURE); + } + + return (Lst_InsertAfter(l, Lst_Last(l), d)); +} + diff --git a/usr.bin/make/lst.lib/lstFind.c b/usr.bin/make/lst.lib/lstFind.c new file mode 100644 index 0000000..d07dbe7 --- /dev/null +++ b/usr.bin/make/lst.lib/lstFind.c @@ -0,0 +1,74 @@ +/* $NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstFind.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstFind.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstFind.c -- + * Find a node on a list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Find -- + * Find a node on the given list using the given comparison function + * and the given datum. + * + * Results: + * The found node or NULL if none matches. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Find(Lst l, const void *d, int (*cProc)(const void *, const void *)) +{ + return (Lst_FindFrom(l, Lst_First(l), d, cProc)); +} + diff --git a/usr.bin/make/lst.lib/lstFindFrom.c b/usr.bin/make/lst.lib/lstFindFrom.c new file mode 100644 index 0000000..e2beab6 --- /dev/null +++ b/usr.bin/make/lst.lib/lstFindFrom.c @@ -0,0 +1,90 @@ +/* $NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstFindFrom.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstFindFrom.c,v 1.15 2009/01/23 21:58:28 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstFindFrom.c -- + * Find a node on a list from a given starting point. Used by Lst_Find. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_FindFrom -- + * Search for a node starting and ending with the given one on the + * given list using the passed datum and comparison function to + * determine when it has been found. + * + * Results: + * The found node or NULL + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_FindFrom(Lst l, LstNode ln, const void *d, + int (*cProc)(const void *, const void *)) +{ + ListNode tln; + + if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { + return NULL; + } + + tln = ln; + + do { + if ((*cProc)(tln->datum, d) == 0) + return (tln); + tln = tln->nextPtr; + } while (tln != ln && tln != NULL); + + return NULL; +} + diff --git a/usr.bin/make/lst.lib/lstFirst.c b/usr.bin/make/lst.lib/lstFirst.c new file mode 100644 index 0000000..4e8334f --- /dev/null +++ b/usr.bin/make/lst.lib/lstFirst.c @@ -0,0 +1,77 @@ +/* $NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstFirst.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstFirst.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstFirst.c -- + * Return the first node of a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_First -- + * Return the first node on the given list. + * + * Results: + * The first node or NULL if the list is empty. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_First(Lst l) +{ + if (!LstValid (l) || LstIsEmpty (l)) { + return NULL; + } else { + return (l->firstPtr); + } +} + diff --git a/usr.bin/make/lst.lib/lstForEach.c b/usr.bin/make/lst.lib/lstForEach.c new file mode 100644 index 0000000..917e4ea --- /dev/null +++ b/usr.bin/make/lst.lib/lstForEach.c @@ -0,0 +1,76 @@ +/* $NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstForEach.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstForEach.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstForeach.c -- + * Perform a given function on all elements of a list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_ForEach -- + * Apply the given function to each element of the given list. The + * function should return 0 if Lst_ForEach should continue and non- + * zero if it should abort. + * + * Results: + * None. + * + * Side Effects: + * Only those created by the passed-in function. + * + *----------------------------------------------------------------------- + */ +/*VARARGS2*/ +int +Lst_ForEach(Lst l, int (*proc)(void *, void *), void *d) +{ + return Lst_ForEachFrom(l, Lst_First(l), proc, d); +} + diff --git a/usr.bin/make/lst.lib/lstForEachFrom.c b/usr.bin/make/lst.lib/lstForEachFrom.c new file mode 100644 index 0000000..c7f44ad --- /dev/null +++ b/usr.bin/make/lst.lib/lstForEachFrom.c @@ -0,0 +1,125 @@ +/* $NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstForEachFrom.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstForEachFrom.c,v 1.17 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * lstForEachFrom.c -- + * Perform a given function on all elements of a list starting from + * a given point. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_ForEachFrom -- + * Apply the given function to each element of the given list. The + * function should return 0 if traversal should continue and non- + * zero if it should abort. + * + * Results: + * None. + * + * Side Effects: + * Only those created by the passed-in function. + * + *----------------------------------------------------------------------- + */ +/*VARARGS2*/ +int +Lst_ForEachFrom(Lst l, LstNode ln, int (*proc)(void *, void *), + void *d) +{ + ListNode tln = ln; + List list = l; + ListNode next; + Boolean done; + int result; + + if (!LstValid (list) || LstIsEmpty (list)) { + return 0; + } + + do { + /* + * Take care of having the current element deleted out from under + * us. + */ + + next = tln->nextPtr; + + /* + * We're done with the traversal if + * - the next node to examine is the first in the queue or + * doesn't exist and + * - nothing's been added after the current node (check this + * after proc() has been called). + */ + done = (next == NULL || next == list->firstPtr); + + (void) tln->useCount++; + result = (*proc) (tln->datum, d); + (void) tln->useCount--; + + /* + * Now check whether a node has been added. + * Note: this doesn't work if this node was deleted before + * the new node was added. + */ + if (next != tln->nextPtr) { + next = tln->nextPtr; + done = 0; + } + + if (tln->flags & LN_DELETED) { + free((char *)tln); + } + tln = next; + } while (!result && !LstIsEmpty(list) && !done); + + return result; +} + diff --git a/usr.bin/make/lst.lib/lstInit.c b/usr.bin/make/lst.lib/lstInit.c new file mode 100644 index 0000000..f98ac42 --- /dev/null +++ b/usr.bin/make/lst.lib/lstInit.c @@ -0,0 +1,85 @@ +/* $NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstInit.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstInit.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * init.c -- + * Initialize a new linked list. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Init -- + * Create and initialize a new list. + * + * Input: + * circ TRUE if the list should be made circular + * + * Results: + * The created list. + * + * Side Effects: + * A list is created, what else? + * + *----------------------------------------------------------------------- + */ +Lst +Lst_Init(Boolean circ) +{ + List nList; + + PAlloc (nList, List); + + nList->firstPtr = NULL; + nList->lastPtr = NULL; + nList->isOpen = FALSE; + nList->isCirc = circ; + nList->atEnd = Unknown; + + return (nList); +} diff --git a/usr.bin/make/lst.lib/lstInsert.c b/usr.bin/make/lst.lib/lstInsert.c new file mode 100644 index 0000000..77187bb --- /dev/null +++ b/usr.bin/make/lst.lib/lstInsert.c @@ -0,0 +1,122 @@ +/* $NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstInsert.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstInsert.c,v 1.14 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstInsert.c -- + * Insert a new datum before an old one + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_InsertBefore -- + * Insert a new node with the given piece of data before the given + * node in the given list. + * + * Input: + * l list to manipulate + * ln node before which to insert d + * d datum to be inserted + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * the firstPtr field will be changed if ln is the first node in the + * list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_InsertBefore(Lst l, LstNode ln, void *d) +{ + ListNode nLNode; /* new lnode for d */ + ListNode lNode = ln; + List list = l; + + + /* + * check validity of arguments + */ + if (LstValid (l) && (LstIsEmpty (l) && ln == NULL)) + goto ok; + + if (!LstValid (l) || LstIsEmpty (l) || !LstNodeValid (ln, l)) { + return (FAILURE); + } + + ok: + PAlloc (nLNode, ListNode); + + nLNode->datum = d; + nLNode->useCount = nLNode->flags = 0; + + if (ln == NULL) { + if (list->isCirc) { + nLNode->prevPtr = nLNode->nextPtr = nLNode; + } else { + nLNode->prevPtr = nLNode->nextPtr = NULL; + } + list->firstPtr = list->lastPtr = nLNode; + } else { + nLNode->prevPtr = lNode->prevPtr; + nLNode->nextPtr = lNode; + + if (nLNode->prevPtr != NULL) { + nLNode->prevPtr->nextPtr = nLNode; + } + lNode->prevPtr = nLNode; + + if (lNode == list->firstPtr) { + list->firstPtr = nLNode; + } + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstInt.h b/usr.bin/make/lst.lib/lstInt.h new file mode 100644 index 0000000..ac53dcb --- /dev/null +++ b/usr.bin/make/lst.lib/lstInt.h @@ -0,0 +1,105 @@ +/* $NetBSD: lstInt.h,v 1.22 2014/09/07 20:55:34 joerg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)lstInt.h 8.1 (Berkeley) 6/6/93 + */ + +/*- + * lstInt.h -- + * Internals for the list library + */ +#ifndef _LSTINT_H_ +#define _LSTINT_H_ + +#include "../lst.h" +#include "../make_malloc.h" + +typedef struct ListNode { + struct ListNode *prevPtr; /* previous element in list */ + struct ListNode *nextPtr; /* next in list */ + unsigned int useCount:8, /* Count of functions using the node. + * node may not be deleted until count + * goes to 0 */ + flags:8; /* Node status flags */ + void *datum; /* datum associated with this element */ +} *ListNode; +/* + * Flags required for synchronization + */ +#define LN_DELETED 0x0001 /* List node should be removed when done */ + +typedef enum { + Head, Middle, Tail, Unknown +} Where; + +typedef struct List { + ListNode firstPtr; /* first node in list */ + ListNode lastPtr; /* last node in list */ + Boolean isCirc; /* true if the list should be considered + * circular */ +/* + * fields for sequential access + */ + Where atEnd; /* Where in the list the last access was */ + Boolean isOpen; /* true if list has been Lst_Open'ed */ + ListNode curPtr; /* current node, if open. NULL if + * *just* opened */ + ListNode prevPtr; /* Previous node, if open. Used by + * Lst_Remove */ +} *List; + +/* + * PAlloc (var, ptype) -- + * Allocate a pointer-typedef structure 'ptype' into the variable 'var' + */ +#define PAlloc(var,ptype) var = (ptype) bmake_malloc(sizeof *(var)) + +/* + * LstValid (l) -- + * Return TRUE if the list l is valid + */ +#define LstValid(l) ((Lst)(l) != NULL) + +/* + * LstNodeValid (ln, l) -- + * Return TRUE if the LstNode ln is valid with respect to l + */ +#define LstNodeValid(ln, l) ((ln) != NULL) + +/* + * LstIsEmpty (l) -- + * TRUE if the list l is empty. + */ +#define LstIsEmpty(l) (((List)(l))->firstPtr == NULL) + +#endif /* _LSTINT_H_ */ diff --git a/usr.bin/make/lst.lib/lstIsAtEnd.c b/usr.bin/make/lst.lib/lstIsAtEnd.c new file mode 100644 index 0000000..70270d2 --- /dev/null +++ b/usr.bin/make/lst.lib/lstIsAtEnd.c @@ -0,0 +1,87 @@ +/* $NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstIsAtEnd.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstIsAtEnd.c,v 1.13 2008/02/15 21:29:50 christos Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstIsAtEnd.c -- + * Tell if the current node is at the end of the list. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_IsAtEnd -- + * Return true if have reached the end of the given list. + * + * Results: + * TRUE if at the end of the list (this includes the list not being + * open or being invalid) or FALSE if not. We return TRUE if the list + * is invalid or unopend so as to cause the caller to exit its loop + * asap, the assumption being that the loop is of the form + * while (!Lst_IsAtEnd (l)) { + * ... + * } + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Lst_IsAtEnd(Lst l) +{ + List list = l; + + return (!LstValid (l) || !list->isOpen || + (list->atEnd == Head) || (list->atEnd == Tail)); +} + diff --git a/usr.bin/make/lst.lib/lstIsEmpty.c b/usr.bin/make/lst.lib/lstIsEmpty.c new file mode 100644 index 0000000..8b1d6ed --- /dev/null +++ b/usr.bin/make/lst.lib/lstIsEmpty.c @@ -0,0 +1,75 @@ +/* $NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstIsEmpty.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstIsEmpty.c,v 1.11 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstIsEmpty.c -- + * A single function to decide if a list is empty + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_IsEmpty -- + * Return TRUE if the given list is empty. + * + * Results: + * TRUE if the list is empty, FALSE otherwise. + * + * Side Effects: + * None. + * + * A list is considered empty if its firstPtr == NULL (or if + * the list itself is NULL). + *----------------------------------------------------------------------- + */ +Boolean +Lst_IsEmpty(Lst l) +{ + return ( ! LstValid (l) || LstIsEmpty(l)); +} + diff --git a/usr.bin/make/lst.lib/lstLast.c b/usr.bin/make/lst.lib/lstLast.c new file mode 100644 index 0000000..096ca24 --- /dev/null +++ b/usr.bin/make/lst.lib/lstLast.c @@ -0,0 +1,77 @@ +/* $NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstLast.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstLast.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstLast.c -- + * Return the last element of a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Last -- + * Return the last node on the list l. + * + * Results: + * The requested node or NULL if the list is empty. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Last(Lst l) +{ + if (!LstValid(l) || LstIsEmpty (l)) { + return NULL; + } else { + return (l->lastPtr); + } +} + diff --git a/usr.bin/make/lst.lib/lstMember.c b/usr.bin/make/lst.lib/lstMember.c new file mode 100644 index 0000000..e9046ac --- /dev/null +++ b/usr.bin/make/lst.lib/lstMember.c @@ -0,0 +1,77 @@ +/* $NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstMember.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstMember.c,v 1.14 2013/11/14 00:01:28 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * lstMember.c -- + * See if a given datum is on a given list. + */ + +#include "lstInt.h" + +LstNode +Lst_Member(Lst l, void *d) +{ + List list = l; + ListNode lNode; + + if (list == NULL) { + return NULL; + } + lNode = list->firstPtr; + if (lNode == NULL) { + return NULL; + } + + do { + if (lNode->datum == d) { + return lNode; + } + lNode = lNode->nextPtr; + } while (lNode != NULL && lNode != list->firstPtr); + + return NULL; +} diff --git a/usr.bin/make/lst.lib/lstNext.c b/usr.bin/make/lst.lib/lstNext.c new file mode 100644 index 0000000..5c2e0ee --- /dev/null +++ b/usr.bin/make/lst.lib/lstNext.c @@ -0,0 +1,120 @@ +/* $NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstNext.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstNext.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstNext.c -- + * Return the next node for a list. + * The sequential functions access the list in a slightly different way. + * CurPtr points to their idea of the current node in the list and they + * access the list based on it. Because the list is circular, Lst_Next + * and Lst_Prev will go around the list forever. Lst_IsAtEnd must be + * used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Next -- + * Return the next node for the given list. + * + * Results: + * The next node or NULL if the list has yet to be opened. Also + * if the list is non-circular and the end has been reached, NULL + * is returned. + * + * Side Effects: + * the curPtr field is updated. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Next(Lst l) +{ + ListNode tln; + List list = l; + + if ((LstValid (l) == FALSE) || + (list->isOpen == FALSE)) { + return NULL; + } + + list->prevPtr = list->curPtr; + + if (list->curPtr == NULL) { + if (list->atEnd == Unknown) { + /* + * If we're just starting out, atEnd will be Unknown. + * Then we want to start this thing off in the right + * direction -- at the start with atEnd being Middle. + */ + list->curPtr = tln = list->firstPtr; + list->atEnd = Middle; + } else { + tln = NULL; + list->atEnd = Tail; + } + } else { + tln = list->curPtr->nextPtr; + list->curPtr = tln; + + if (tln == list->firstPtr || tln == NULL) { + /* + * If back at the front, then we've hit the end... + */ + list->atEnd = Tail; + } else { + /* + * Reset to Middle if gone past first. + */ + list->atEnd = Middle; + } + } + + return (tln); +} + diff --git a/usr.bin/make/lst.lib/lstOpen.c b/usr.bin/make/lst.lib/lstOpen.c new file mode 100644 index 0000000..941293e --- /dev/null +++ b/usr.bin/make/lst.lib/lstOpen.c @@ -0,0 +1,87 @@ +/* $NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstOpen.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstOpen.c,v 1.12 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstOpen.c -- + * Open a list for sequential access. The sequential functions access the + * list in a slightly different way. CurPtr points to their idea of the + * current node in the list and they access the list based on it. + * If the list is circular, Lst_Next and Lst_Prev will go around + * the list forever. Lst_IsAtEnd must be used to determine when to stop. + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Open -- + * Open a list for sequential access. A list can still be searched, + * etc., without confusing these functions. + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * isOpen is set TRUE and curPtr is set to NULL so the + * other sequential functions no it was just opened and can choose + * the first element accessed based on this. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Open(Lst l) +{ + if (LstValid (l) == FALSE) { + return (FAILURE); + } + (l)->isOpen = TRUE; + (l)->atEnd = LstIsEmpty (l) ? Head : Unknown; + (l)->curPtr = NULL; + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstPrev.c b/usr.bin/make/lst.lib/lstPrev.c new file mode 100644 index 0000000..0ec865d --- /dev/null +++ b/usr.bin/make/lst.lib/lstPrev.c @@ -0,0 +1,79 @@ +/* $NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstPrev.c,v 1.3 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstPrev.c -- + * return the predecessor to a given node + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Prev -- + * Return the predecessor to the given node on its list. + * + * Results: + * The predecessor of the node, if it exists (note that on a circular + * list, if the node is the only one in the list, it is its own + * predecessor). + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Prev(LstNode ln) +{ + if (ln == NULL) { + return NULL; + } else { + return (ln->prevPtr); + } +} + diff --git a/usr.bin/make/lst.lib/lstRemove.c b/usr.bin/make/lst.lib/lstRemove.c new file mode 100644 index 0000000..7480d30 --- /dev/null +++ b/usr.bin/make/lst.lib/lstRemove.c @@ -0,0 +1,136 @@ +/* $NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstRemove.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstRemove.c,v 1.16 2014/09/07 20:55:34 joerg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstRemove.c -- + * Remove an element from a list + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Remove -- + * Remove the given node from the given list. + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * The list's firstPtr will be set to NULL if ln is the last + * node on the list. firsPtr and lastPtr will be altered if ln is + * either the first or last node, respectively, on the list. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Remove(Lst l, LstNode ln) +{ + List list = l; + ListNode lNode = ln; + + if (!LstValid (l) || + !LstNodeValid (ln, l)) { + return (FAILURE); + } + + /* + * unlink it from the list + */ + if (lNode->nextPtr != NULL) { + lNode->nextPtr->prevPtr = lNode->prevPtr; + } + if (lNode->prevPtr != NULL) { + lNode->prevPtr->nextPtr = lNode->nextPtr; + } + + /* + * if either the firstPtr or lastPtr of the list point to this node, + * adjust them accordingly + */ + if (list->firstPtr == lNode) { + list->firstPtr = lNode->nextPtr; + } + if (list->lastPtr == lNode) { + list->lastPtr = lNode->prevPtr; + } + + /* + * Sequential access stuff. If the node we're removing is the current + * node in the list, reset the current node to the previous one. If the + * previous one was non-existent (prevPtr == NULL), we set the + * end to be Unknown, since it is. + */ + if (list->isOpen && (list->curPtr == lNode)) { + list->curPtr = list->prevPtr; + if (list->curPtr == NULL) { + list->atEnd = Unknown; + } + } + + /* + * the only way firstPtr can still point to ln is if ln is the last + * node on the list (the list is circular, so lNode->nextptr == lNode in + * this case). The list is, therefore, empty and is marked as such + */ + if (list->firstPtr == lNode) { + list->firstPtr = NULL; + } + + /* + * note that the datum is unmolested. The caller must free it as + * necessary and as expected. + */ + if (lNode->useCount == 0) { + free(ln); + } else { + lNode->flags |= LN_DELETED; + } + + return (SUCCESS); +} + diff --git a/usr.bin/make/lst.lib/lstReplace.c b/usr.bin/make/lst.lib/lstReplace.c new file mode 100644 index 0000000..090e91a --- /dev/null +++ b/usr.bin/make/lst.lib/lstReplace.c @@ -0,0 +1,78 @@ +/* $NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstReplace.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstReplace.c,v 1.13 2009/01/23 21:26:30 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstReplace.c -- + * Replace the datum in a node with a new datum + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Replace -- + * Replace the datum in the given node with the new datum + * + * Results: + * SUCCESS or FAILURE. + * + * Side Effects: + * The datum field fo the node is altered. + * + *----------------------------------------------------------------------- + */ +ReturnStatus +Lst_Replace(LstNode ln, void *d) +{ + if (ln == NULL) { + return (FAILURE); + } else { + (ln)->datum = d; + return (SUCCESS); + } +} + diff --git a/usr.bin/make/lst.lib/lstSucc.c b/usr.bin/make/lst.lib/lstSucc.c new file mode 100644 index 0000000..3f13aa5 --- /dev/null +++ b/usr.bin/make/lst.lib/lstSucc.c @@ -0,0 +1,79 @@ +/* $NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)lstSucc.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: lstSucc.c,v 1.13 2008/12/13 15:19:29 dsl Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * LstSucc.c -- + * return the successor to a given node + */ + +#include "lstInt.h" + +/*- + *----------------------------------------------------------------------- + * Lst_Succ -- + * Return the successor to the given node on its list. + * + * Results: + * The successor of the node, if it exists (note that on a circular + * list, if the node is the only one in the list, it is its own + * successor). + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +LstNode +Lst_Succ(LstNode ln) +{ + if (ln == NULL) { + return NULL; + } else { + return (ln->nextPtr); + } +} + diff --git a/usr.bin/make/main.c b/usr.bin/make/main.c new file mode 100644 index 0000000..a9e756a --- /dev/null +++ b/usr.bin/make/main.c @@ -0,0 +1,2189 @@ +/* $NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $"; +#else +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1989, 1990, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)main.c 8.3 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: main.c,v 1.273 2017/10/28 21:54:54 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * main.c -- + * The main file for this entire program. Exit routines etc + * reside here. + * + * Utility functions defined in this file: + * Main_ParseArgLine Takes a line of arguments, breaks them and + * treats them as if they were given when first + * invoked. Used by the parse module to implement + * the .MFLAGS target. + * + * Error Print a tagged error message. The global + * MAKE variable must have been defined. This + * takes a format string and optional arguments + * for it. + * + * Fatal Print an error message and exit. Also takes + * a format string and arguments for it. + * + * Punt Aborts all jobs and exits with a message. Also + * takes a format string and arguments for it. + * + * Finish Finish things up by printing the number of + * errors which occurred, as passed to it, and + * exiting. + */ + +#include +#include +#include +#include +#include +#ifdef MAKE_NATIVE +#include +#endif +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" +#include "pathnames.h" +#include "trace.h" + +#ifdef USE_IOVEC +#include +#endif + +#ifndef DEFMAXLOCAL +#define DEFMAXLOCAL DEFMAXJOBS +#endif /* DEFMAXLOCAL */ + +Lst create; /* Targets to be made */ +time_t now; /* Time at start of make */ +GNode *DEFAULT; /* .DEFAULT node */ +Boolean allPrecious; /* .PRECIOUS given on line by itself */ +Boolean deleteOnError; /* .DELETE_ON_ERROR: set */ + +static Boolean noBuiltins; /* -r flag */ +static Lst makefiles; /* ordered list of makefiles to read */ +static int printVars; /* -[vV] argument */ +#define COMPAT_VARS 1 +#define EXPAND_VARS 2 +static Lst variables; /* list of variables to print */ +int maxJobs; /* -j argument */ +static int maxJobTokens; /* -j argument */ +Boolean compatMake; /* -B argument */ +int debug; /* -d argument */ +Boolean debugVflag; /* -dV */ +Boolean noExecute; /* -n flag */ +Boolean noRecursiveExecute; /* -N flag */ +Boolean keepgoing; /* -k flag */ +Boolean queryFlag; /* -q flag */ +Boolean touchFlag; /* -t flag */ +Boolean enterFlag; /* -w flag */ +Boolean enterFlagObj; /* -w and objdir != srcdir */ +Boolean ignoreErrors; /* -i flag */ +Boolean beSilent; /* -s flag */ +Boolean oldVars; /* variable substitution style */ +Boolean checkEnvFirst; /* -e flag */ +Boolean parseWarnFatal; /* -W flag */ +Boolean jobServer; /* -J flag */ +static int jp_0 = -1, jp_1 = -1; /* ends of parent job pipe */ +Boolean varNoExportEnv; /* -X flag */ +Boolean doing_depend; /* Set while reading .depend */ +static Boolean jobsRunning; /* TRUE if the jobs might be running */ +static const char * tracefile; +static void MainParseArgs(int, char **); +static int ReadMakefile(const void *, const void *); +static void usage(void) MAKE_ATTR_DEAD; +static void purge_cached_realpaths(void); + +static Boolean ignorePWD; /* if we use -C, PWD is meaningless */ +static char objdir[MAXPATHLEN + 1]; /* where we chdir'ed to */ +char curdir[MAXPATHLEN + 1]; /* Startup directory */ +char *progname; /* the program name */ +char *makeDependfile; +pid_t myPid; +int makelevel; + +Boolean forceJobs = FALSE; + +extern Lst parseIncPath; + +/* + * For compatibility with the POSIX version of MAKEFLAGS that includes + * all the options with out -, convert flags to -f -l -a -g -s. + */ +static char * +explode(const char *flags) +{ + size_t len; + char *nf, *st; + const char *f; + + if (flags == NULL) + return NULL; + + for (f = flags; *f; f++) + if (!isalpha((unsigned char)*f)) + break; + + if (*f) + return bmake_strdup(flags); + + len = strlen(flags); + st = nf = bmake_malloc(len * 3 + 1); + while (*flags) { + *nf++ = '-'; + *nf++ = *flags++; + *nf++ = ' '; + } + *nf = '\0'; + return st; +} + +static void +parse_debug_options(const char *argvalue) +{ + const char *modules; + const char *mode; + char *fname; + int len; + + for (modules = argvalue; *modules; ++modules) { + switch (*modules) { + case 'A': + debug = ~0; + break; + case 'a': + debug |= DEBUG_ARCH; + break; + case 'C': + debug |= DEBUG_CWD; + break; + case 'c': + debug |= DEBUG_COND; + break; + case 'd': + debug |= DEBUG_DIR; + break; + case 'e': + debug |= DEBUG_ERROR; + break; + case 'f': + debug |= DEBUG_FOR; + break; + case 'g': + if (modules[1] == '1') { + debug |= DEBUG_GRAPH1; + ++modules; + } + else if (modules[1] == '2') { + debug |= DEBUG_GRAPH2; + ++modules; + } + else if (modules[1] == '3') { + debug |= DEBUG_GRAPH3; + ++modules; + } + break; + case 'j': + debug |= DEBUG_JOB; + break; + case 'l': + debug |= DEBUG_LOUD; + break; + case 'M': + debug |= DEBUG_META; + break; + case 'm': + debug |= DEBUG_MAKE; + break; + case 'n': + debug |= DEBUG_SCRIPT; + break; + case 'p': + debug |= DEBUG_PARSE; + break; + case 's': + debug |= DEBUG_SUFF; + break; + case 't': + debug |= DEBUG_TARG; + break; + case 'V': + debugVflag = TRUE; + break; + case 'v': + debug |= DEBUG_VAR; + break; + case 'x': + debug |= DEBUG_SHELL; + break; + case 'F': + if (debug_file != stdout && debug_file != stderr) + fclose(debug_file); + if (*++modules == '+') { + modules++; + mode = "a"; + } else + mode = "w"; + if (strcmp(modules, "stdout") == 0) { + debug_file = stdout; + goto debug_setbuf; + } + if (strcmp(modules, "stderr") == 0) { + debug_file = stderr; + goto debug_setbuf; + } + len = strlen(modules); + fname = bmake_malloc(len + 20); + memcpy(fname, modules, len + 1); + /* Let the filename be modified by the pid */ + if (strcmp(fname + len - 3, ".%d") == 0) + snprintf(fname + len - 2, 20, "%d", getpid()); + debug_file = fopen(fname, mode); + if (!debug_file) { + fprintf(stderr, "Cannot open debug file %s\n", + fname); + usage(); + } + free(fname); + goto debug_setbuf; + default: + (void)fprintf(stderr, + "%s: illegal argument to d option -- %c\n", + progname, *modules); + usage(); + } + } +debug_setbuf: + /* + * Make the debug_file unbuffered, and make + * stdout line buffered (unless debugfile == stdout). + */ + setvbuf(debug_file, NULL, _IONBF, 0); + if (debug_file != stdout) { + setvbuf(stdout, NULL, _IOLBF, 0); + } +} + +/* + * does path contain any relative components + */ +static int +is_relpath(const char *path) +{ + const char *cp; + + if (path[0] != '/') + return TRUE; + cp = path; + do { + cp = strstr(cp, "/."); + if (!cp) + break; + cp += 2; + if (cp[0] == '/' || cp[0] == '\0') + return TRUE; + else if (cp[0] == '.') { + if (cp[1] == '/' || cp[1] == '\0') + return TRUE; + } + } while (cp); + return FALSE; +} + +/*- + * MainParseArgs -- + * Parse a given argument vector. Called from main() and from + * Main_ParseArgLine() when the .MAKEFLAGS target is used. + * + * XXX: Deal with command line overriding .MAKEFLAGS in makefile + * + * Results: + * None + * + * Side Effects: + * Various global and local flags will be set depending on the flags + * given + */ +static void +MainParseArgs(int argc, char **argv) +{ + char *p; + int c = '?'; + int arginc; + char *argvalue; + const char *getopt_def; + struct stat sa, sb; + char *optscan; + Boolean inOption, dashDash = FALSE; + char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ + +#define OPTFLAGS "BC:D:I:J:NST:V:WXd:ef:ij:km:nqrstv:w" +/* Can't actually use getopt(3) because rescanning is not portable */ + + getopt_def = OPTFLAGS; +rearg: + inOption = FALSE; + optscan = NULL; + while(argc > 1) { + char *getopt_spec; + if(!inOption) + optscan = argv[1]; + c = *optscan++; + arginc = 0; + if(inOption) { + if(c == '\0') { + ++argv; + --argc; + inOption = FALSE; + continue; + } + } else { + if (c != '-' || dashDash) + break; + inOption = TRUE; + c = *optscan++; + } + /* '-' found at some earlier point */ + getopt_spec = strchr(getopt_def, c); + if(c != '\0' && getopt_spec != NULL && getopt_spec[1] == ':') { + /* - found, and should have an arg */ + inOption = FALSE; + arginc = 1; + argvalue = optscan; + if(*argvalue == '\0') { + if (argc < 3) + goto noarg; + argvalue = argv[2]; + arginc = 2; + } + } else { + argvalue = NULL; + } + switch(c) { + case '\0': + arginc = 1; + inOption = FALSE; + break; + case 'B': + compatMake = TRUE; + Var_Append(MAKEFLAGS, "-B", VAR_GLOBAL); + Var_Set(MAKE_MODE, "compat", VAR_GLOBAL, 0); + break; + case 'C': + if (chdir(argvalue) == -1) { + (void)fprintf(stderr, + "%s: chdir %s: %s\n", + progname, argvalue, + strerror(errno)); + exit(1); + } + if (getcwd(curdir, MAXPATHLEN) == NULL) { + (void)fprintf(stderr, "%s: %s.\n", progname, strerror(errno)); + exit(2); + } + if (!is_relpath(argvalue) && + stat(argvalue, &sa) != -1 && + stat(curdir, &sb) != -1 && + sa.st_ino == sb.st_ino && + sa.st_dev == sb.st_dev) + strncpy(curdir, argvalue, MAXPATHLEN); + ignorePWD = TRUE; + break; + case 'D': + if (argvalue == NULL || argvalue[0] == 0) goto noarg; + Var_Set(argvalue, "1", VAR_GLOBAL, 0); + Var_Append(MAKEFLAGS, "-D", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'I': + if (argvalue == NULL) goto noarg; + Parse_AddIncludeDir(argvalue); + Var_Append(MAKEFLAGS, "-I", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'J': + if (argvalue == NULL) goto noarg; + if (sscanf(argvalue, "%d,%d", &jp_0, &jp_1) != 2) { + (void)fprintf(stderr, + "%s: internal error -- J option malformed (%s)\n", + progname, argvalue); + usage(); + } + if ((fcntl(jp_0, F_GETFD, 0) < 0) || + (fcntl(jp_1, F_GETFD, 0) < 0)) { +#if 0 + (void)fprintf(stderr, + "%s: ###### warning -- J descriptors were closed!\n", + progname); + exit(2); +#endif + jp_0 = -1; + jp_1 = -1; + compatMake = TRUE; + } else { + Var_Append(MAKEFLAGS, "-J", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + jobServer = TRUE; + } + break; + case 'N': + noExecute = TRUE; + noRecursiveExecute = TRUE; + Var_Append(MAKEFLAGS, "-N", VAR_GLOBAL); + break; + case 'S': + keepgoing = FALSE; + Var_Append(MAKEFLAGS, "-S", VAR_GLOBAL); + break; + case 'T': + if (argvalue == NULL) goto noarg; + tracefile = bmake_strdup(argvalue); + Var_Append(MAKEFLAGS, "-T", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'V': + case 'v': + if (argvalue == NULL) goto noarg; + printVars = c == 'v' ? EXPAND_VARS : COMPAT_VARS; + (void)Lst_AtEnd(variables, argvalue); + Var_Append(MAKEFLAGS, "-V", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'W': + parseWarnFatal = TRUE; + break; + case 'X': + varNoExportEnv = TRUE; + Var_Append(MAKEFLAGS, "-X", VAR_GLOBAL); + break; + case 'd': + if (argvalue == NULL) goto noarg; + /* If '-d-opts' don't pass to children */ + if (argvalue[0] == '-') + argvalue++; + else { + Var_Append(MAKEFLAGS, "-d", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + } + parse_debug_options(argvalue); + break; + case 'e': + checkEnvFirst = TRUE; + Var_Append(MAKEFLAGS, "-e", VAR_GLOBAL); + break; + case 'f': + if (argvalue == NULL) goto noarg; + (void)Lst_AtEnd(makefiles, argvalue); + break; + case 'i': + ignoreErrors = TRUE; + Var_Append(MAKEFLAGS, "-i", VAR_GLOBAL); + break; + case 'j': + if (argvalue == NULL) goto noarg; + forceJobs = TRUE; + maxJobs = strtol(argvalue, &p, 0); + if (*p != '\0' || maxJobs < 1) { + (void)fprintf(stderr, "%s: illegal argument to -j -- must be positive integer!\n", + progname); + exit(1); + } + Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + Var_Set(".MAKE.JOBS", argvalue, VAR_GLOBAL, 0); + maxJobTokens = maxJobs; + break; + case 'k': + keepgoing = TRUE; + Var_Append(MAKEFLAGS, "-k", VAR_GLOBAL); + break; + case 'm': + if (argvalue == NULL) goto noarg; + /* look for magic parent directory search string */ + if (strncmp(".../", argvalue, 4) == 0) { + if (!Dir_FindHereOrAbove(curdir, argvalue+4, + found_path, sizeof(found_path))) + break; /* nothing doing */ + (void)Dir_AddDir(sysIncPath, found_path); + } else { + (void)Dir_AddDir(sysIncPath, argvalue); + } + Var_Append(MAKEFLAGS, "-m", VAR_GLOBAL); + Var_Append(MAKEFLAGS, argvalue, VAR_GLOBAL); + break; + case 'n': + noExecute = TRUE; + Var_Append(MAKEFLAGS, "-n", VAR_GLOBAL); + break; + case 'q': + queryFlag = TRUE; + /* Kind of nonsensical, wot? */ + Var_Append(MAKEFLAGS, "-q", VAR_GLOBAL); + break; + case 'r': + noBuiltins = TRUE; + Var_Append(MAKEFLAGS, "-r", VAR_GLOBAL); + break; + case 's': + beSilent = TRUE; + Var_Append(MAKEFLAGS, "-s", VAR_GLOBAL); + break; + case 't': + touchFlag = TRUE; + Var_Append(MAKEFLAGS, "-t", VAR_GLOBAL); + break; + case 'w': + enterFlag = TRUE; + Var_Append(MAKEFLAGS, "-w", VAR_GLOBAL); + break; + case '-': + dashDash = TRUE; + break; + default: + case '?': + usage(); + } + argv += arginc; + argc -= arginc; + } + + oldVars = TRUE; + + /* + * See if the rest of the arguments are variable assignments and + * perform them if so. Else take them to be targets and stuff them + * on the end of the "create" list. + */ + for (; argc > 1; ++argv, --argc) + if (Parse_IsVar(argv[1])) { + Parse_DoVar(argv[1], VAR_CMD); + } else { + if (!*argv[1]) + Punt("illegal (null) argument."); + if (*argv[1] == '-' && !dashDash) + goto rearg; + (void)Lst_AtEnd(create, bmake_strdup(argv[1])); + } + + return; +noarg: + (void)fprintf(stderr, "%s: option requires an argument -- %c\n", + progname, c); + usage(); +} + +/*- + * Main_ParseArgLine -- + * Used by the parse module when a .MFLAGS or .MAKEFLAGS target + * is encountered and by main() when reading the .MAKEFLAGS envariable. + * Takes a line of arguments and breaks it into its + * component words and passes those words and the number of them to the + * MainParseArgs function. + * The line should have all its leading whitespace removed. + * + * Input: + * line Line to fracture + * + * Results: + * None + * + * Side Effects: + * Only those that come from the various arguments. + */ +void +Main_ParseArgLine(const char *line) +{ + char **argv; /* Manufactured argument vector */ + int argc; /* Number of arguments in argv */ + char *args; /* Space used by the args */ + char *buf, *p1; + char *argv0 = Var_Value(".MAKE", VAR_GLOBAL, &p1); + size_t len; + + if (line == NULL) + return; + for (; *line == ' '; ++line) + continue; + if (!*line) + return; + + buf = bmake_malloc(len = strlen(line) + strlen(argv0) + 2); + (void)snprintf(buf, len, "%s %s", argv0, line); + free(p1); + + argv = brk_string(buf, &argc, TRUE, &args); + if (argv == NULL) { + Error("Unterminated quoted string [%s]", buf); + free(buf); + return; + } + free(buf); + MainParseArgs(argc, argv); + + free(args); + free(argv); +} + +Boolean +Main_SetObjdir(const char *fmt, ...) +{ + struct stat sb; + char *path; + char buf[MAXPATHLEN + 1]; + char buf2[MAXPATHLEN + 1]; + Boolean rc = FALSE; + va_list ap; + + va_start(ap, fmt); + vsnprintf(path = buf, MAXPATHLEN, fmt, ap); + va_end(ap); + + if (path[0] != '/') { + snprintf(buf2, MAXPATHLEN, "%s/%s", curdir, path); + path = buf2; + } + + /* look for the directory and try to chdir there */ + if (stat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + if (chdir(path)) { + (void)fprintf(stderr, "make warning: %s: %s.\n", + path, strerror(errno)); + } else { + strncpy(objdir, path, MAXPATHLEN); + Var_Set(".OBJDIR", objdir, VAR_GLOBAL, 0); + setenv("PWD", objdir, 1); + Dir_InitDot(); + purge_cached_realpaths(); + rc = TRUE; + if (enterFlag && strcmp(objdir, curdir) != 0) + enterFlagObj = TRUE; + } + } + + return rc; +} + +static Boolean +Main_SetVarObjdir(const char *var, const char *suffix) +{ + char *p, *path, *xpath; + + if ((path = Var_Value(var, VAR_CMD, &p)) == NULL || + *path == '\0') + return FALSE; + + /* expand variable substitutions */ + if (strchr(path, '$') != 0) + xpath = Var_Subst(NULL, path, VAR_GLOBAL, VARF_WANTRES); + else + xpath = path; + + (void)Main_SetObjdir("%s%s", xpath, suffix); + + if (xpath != path) + free(xpath); + free(p); + return TRUE; +} + +/*- + * ReadAllMakefiles -- + * wrapper around ReadMakefile() to read all. + * + * Results: + * TRUE if ok, FALSE on error + */ +static int +ReadAllMakefiles(const void *p, const void *q) +{ + return (ReadMakefile(p, q) == 0); +} + +int +str2Lst_Append(Lst lp, char *str, const char *sep) +{ + char *cp; + int n; + + if (!sep) + sep = " \t"; + + for (n = 0, cp = strtok(str, sep); cp; cp = strtok(NULL, sep)) { + (void)Lst_AtEnd(lp, cp); + n++; + } + return (n); +} + +#ifdef SIGINFO +/*ARGSUSED*/ +static void +siginfo(int signo MAKE_ATTR_UNUSED) +{ + char dir[MAXPATHLEN]; + char str[2 * MAXPATHLEN]; + int len; + if (getcwd(dir, sizeof(dir)) == NULL) + return; + len = snprintf(str, sizeof(str), "%s: Working in: %s\n", progname, dir); + if (len > 0) + (void)write(STDERR_FILENO, str, (size_t)len); +} +#endif + +/* + * Allow makefiles some control over the mode we run in. + */ +void +MakeMode(const char *mode) +{ + char *mp = NULL; + + if (!mode) + mode = mp = Var_Subst(NULL, "${" MAKE_MODE ":tl}", + VAR_GLOBAL, VARF_WANTRES); + + if (mode && *mode) { + if (strstr(mode, "compat")) { + compatMake = TRUE; + forceJobs = FALSE; + } +#if USE_META + if (strstr(mode, "meta")) + meta_mode_init(mode); +#endif + } + + free(mp); +} + +static void +doPrintVars(void) +{ + LstNode ln; + Boolean expandVars; + + if (printVars == EXPAND_VARS) + expandVars = TRUE; + else if (debugVflag) + expandVars = FALSE; + else + expandVars = getBoolean(".MAKE.EXPAND_VARIABLES", FALSE); + + for (ln = Lst_First(variables); ln != NULL; + ln = Lst_Succ(ln)) { + char *var = (char *)Lst_Datum(ln); + char *value; + char *p1; + + if (strchr(var, '$')) { + value = p1 = Var_Subst(NULL, var, VAR_GLOBAL, + VARF_WANTRES); + } else if (expandVars) { + char tmp[128]; + int len = snprintf(tmp, sizeof(tmp), "${%s}", var); + + if (len >= (int)sizeof(tmp)) + Fatal("%s: variable name too big: %s", + progname, var); + value = p1 = Var_Subst(NULL, tmp, VAR_GLOBAL, + VARF_WANTRES); + } else { + value = Var_Value(var, VAR_GLOBAL, &p1); + } + printf("%s\n", value ? value : ""); + free(p1); + } +} + +static Boolean +runTargets(void) +{ + Lst targs; /* target nodes to create -- passed to Make_Init */ + Boolean outOfDate; /* FALSE if all targets up to date */ + + /* + * Have now read the entire graph and need to make a list of + * targets to create. If none was given on the command line, + * we consult the parsing module to find the main target(s) + * to create. + */ + if (Lst_IsEmpty(create)) + targs = Parse_MainName(); + else + targs = Targ_FindList(create, TARG_CREATE); + + if (!compatMake) { + /* + * Initialize job module before traversing the graph + * now that any .BEGIN and .END targets have been read. + * This is done only if the -q flag wasn't given + * (to prevent the .BEGIN from being executed should + * it exist). + */ + if (!queryFlag) { + Job_Init(); + jobsRunning = TRUE; + } + + /* Traverse the graph, checking on all the targets */ + outOfDate = Make_Run(targs); + } else { + /* + * Compat_Init will take care of creating all the + * targets as well as initializing the module. + */ + Compat_Run(targs); + outOfDate = FALSE; + } + Lst_Destroy(targs, NULL); + return outOfDate; +} + +/*- + * main -- + * The main function, for obvious reasons. Initializes variables + * and a few modules, then parses the arguments give it in the + * environment and on the command line. Reads the system makefile + * followed by either Makefile, makefile or the file given by the + * -f argument. Sets the .MAKEFLAGS PMake variable based on all the + * flags it has received by then uses either the Make or the Compat + * module to create the initial list of targets. + * + * Results: + * If -q was given, exits -1 if anything was out-of-date. Else it exits + * 0. + * + * Side Effects: + * The program exits when done. Targets are created. etc. etc. etc. + */ +int +main(int argc, char **argv) +{ + Boolean outOfDate; /* FALSE if all targets up to date */ + struct stat sb, sa; + char *p1, *path; + char mdpath[MAXPATHLEN]; + const char *machine = getenv("MACHINE"); + const char *machine_arch = getenv("MACHINE_ARCH"); + char *syspath = getenv("MAKESYSPATH"); + Lst sysMkPath; /* Path of sys.mk */ + char *cp = NULL, *start; + /* avoid faults on read-only strings */ + static char defsyspath[] = _PATH_DEFSYSPATH; + char found_path[MAXPATHLEN + 1]; /* for searching for sys.mk */ + struct timeval rightnow; /* to initialize random seed */ + struct utsname utsname; + + /* default to writing debug to stderr */ + debug_file = stderr; + +#ifdef SIGINFO + (void)bmake_signal(SIGINFO, siginfo); +#endif + /* + * Set the seed to produce a different random sequence + * on each program execution. + */ + gettimeofday(&rightnow, NULL); + srandom(rightnow.tv_sec + rightnow.tv_usec); + + if ((progname = strrchr(argv[0], '/')) != NULL) + progname++; + else + progname = argv[0]; +#if defined(MAKE_NATIVE) || (defined(HAVE_SETRLIMIT) && defined(RLIMIT_NOFILE)) + /* + * get rid of resource limit on file descriptors + */ + { + struct rlimit rl; + if (getrlimit(RLIMIT_NOFILE, &rl) != -1 && + rl.rlim_cur != rl.rlim_max) { + rl.rlim_cur = rl.rlim_max; + (void)setrlimit(RLIMIT_NOFILE, &rl); + } + } +#endif + + if (uname(&utsname) == -1) { + (void)fprintf(stderr, "%s: uname failed (%s).\n", progname, + strerror(errno)); + exit(2); + } + + /* + * Get the name of this type of MACHINE from utsname + * so we can share an executable for similar machines. + * (i.e. m68k: amiga hp300, mac68k, sun3, ...) + * + * Note that both MACHINE and MACHINE_ARCH are decided at + * run-time. + */ + if (!machine) { +#ifdef MAKE_NATIVE + machine = utsname.machine; +#else +#ifdef MAKE_MACHINE + machine = MAKE_MACHINE; +#else + machine = "unknown"; +#endif +#endif + } + + if (!machine_arch) { +#ifdef MAKE_NATIVE + static char machine_arch_buf[sizeof(utsname.machine)]; + const int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; + size_t len = sizeof(machine_arch_buf); + + if (sysctl(mib, __arraycount(mib), machine_arch_buf, + &len, NULL, 0) < 0) { + (void)fprintf(stderr, "%s: sysctl failed (%s).\n", progname, + strerror(errno)); + exit(2); + } + + machine_arch = machine_arch_buf; +#else +#ifndef MACHINE_ARCH +#ifdef MAKE_MACHINE_ARCH + machine_arch = MAKE_MACHINE_ARCH; +#else + machine_arch = "unknown"; +#endif +#else + machine_arch = MACHINE_ARCH; +#endif +#endif + } + + myPid = getpid(); /* remember this for vFork() */ + + /* + * Just in case MAKEOBJDIR wants us to do something tricky. + */ + Var_Init(); /* Initialize the lists of variables for + * parsing arguments */ + Var_Set(".MAKE.OS", utsname.sysname, VAR_GLOBAL, 0); + Var_Set("MACHINE", machine, VAR_GLOBAL, 0); + Var_Set("MACHINE_ARCH", machine_arch, VAR_GLOBAL, 0); +#ifdef MAKE_VERSION + Var_Set("MAKE_VERSION", MAKE_VERSION, VAR_GLOBAL, 0); +#endif + Var_Set(".newline", "\n", VAR_GLOBAL, 0); /* handy for :@ loops */ + /* + * This is the traditional preference for makefiles. + */ +#ifndef MAKEFILE_PREFERENCE_LIST +# define MAKEFILE_PREFERENCE_LIST "makefile Makefile" +#endif + Var_Set(MAKEFILE_PREFERENCE, MAKEFILE_PREFERENCE_LIST, + VAR_GLOBAL, 0); + Var_Set(MAKE_DEPENDFILE, ".depend", VAR_GLOBAL, 0); + + create = Lst_Init(FALSE); + makefiles = Lst_Init(FALSE); + printVars = 0; + debugVflag = FALSE; + variables = Lst_Init(FALSE); + beSilent = FALSE; /* Print commands as executed */ + ignoreErrors = FALSE; /* Pay attention to non-zero returns */ + noExecute = FALSE; /* Execute all commands */ + noRecursiveExecute = FALSE; /* Execute all .MAKE targets */ + keepgoing = FALSE; /* Stop on error */ + allPrecious = FALSE; /* Remove targets when interrupted */ + deleteOnError = FALSE; /* Historical default behavior */ + queryFlag = FALSE; /* This is not just a check-run */ + noBuiltins = FALSE; /* Read the built-in rules */ + touchFlag = FALSE; /* Actually update targets */ + debug = 0; /* No debug verbosity, please. */ + jobsRunning = FALSE; + + maxJobs = DEFMAXLOCAL; /* Set default local max concurrency */ + maxJobTokens = maxJobs; + compatMake = FALSE; /* No compat mode */ + ignorePWD = FALSE; + + /* + * Initialize the parsing, directory and variable modules to prepare + * for the reading of inclusion paths and variable settings on the + * command line + */ + + /* + * Initialize various variables. + * MAKE also gets this name, for compatibility + * .MAKEFLAGS gets set to the empty string just in case. + * MFLAGS also gets initialized empty, for compatibility. + */ + Parse_Init(); + if (argv[0][0] == '/' || strchr(argv[0], '/') == NULL) { + /* + * Leave alone if it is an absolute path, or if it does + * not contain a '/' in which case we need to find it in + * the path, like execvp(3) and the shells do. + */ + p1 = argv[0]; + } else { + /* + * A relative path, canonicalize it. + */ + p1 = cached_realpath(argv[0], mdpath); + if (!p1 || *p1 != '/' || stat(p1, &sb) < 0) { + p1 = argv[0]; /* realpath failed */ + } + } + Var_Set("MAKE", p1, VAR_GLOBAL, 0); + Var_Set(".MAKE", p1, VAR_GLOBAL, 0); + Var_Set(MAKEFLAGS, "", VAR_GLOBAL, 0); + Var_Set(MAKEOVERRIDES, "", VAR_GLOBAL, 0); + Var_Set("MFLAGS", "", VAR_GLOBAL, 0); + Var_Set(".ALLTARGETS", "", VAR_GLOBAL, 0); + /* some makefiles need to know this */ + Var_Set(MAKE_LEVEL ".ENV", MAKE_LEVEL_ENV, VAR_CMD, 0); + + /* + * Set some other useful macros + */ + { + char tmp[64], *ep; + + makelevel = ((ep = getenv(MAKE_LEVEL_ENV)) && *ep) ? atoi(ep) : 0; + if (makelevel < 0) + makelevel = 0; + snprintf(tmp, sizeof(tmp), "%d", makelevel); + Var_Set(MAKE_LEVEL, tmp, VAR_GLOBAL, 0); + snprintf(tmp, sizeof(tmp), "%u", myPid); + Var_Set(".MAKE.PID", tmp, VAR_GLOBAL, 0); + snprintf(tmp, sizeof(tmp), "%u", getppid()); + Var_Set(".MAKE.PPID", tmp, VAR_GLOBAL, 0); + } + if (makelevel > 0) { + char pn[1024]; + snprintf(pn, sizeof(pn), "%s[%d]", progname, makelevel); + progname = bmake_strdup(pn); + } + +#ifdef USE_META + meta_init(); +#endif + Dir_Init(NULL); /* Dir_* safe to call from MainParseArgs */ + + /* + * First snag any flags out of the MAKE environment variable. + * (Note this is *not* MAKEFLAGS since /bin/make uses that and it's + * in a different format). + */ +#ifdef POSIX + p1 = explode(getenv("MAKEFLAGS")); + Main_ParseArgLine(p1); + free(p1); +#else + Main_ParseArgLine(getenv("MAKE")); +#endif + + /* + * Find where we are (now). + * We take care of PWD for the automounter below... + */ + if (getcwd(curdir, MAXPATHLEN) == NULL) { + (void)fprintf(stderr, "%s: getcwd: %s.\n", + progname, strerror(errno)); + exit(2); + } + + MainParseArgs(argc, argv); + + if (enterFlag) + printf("%s: Entering directory `%s'\n", progname, curdir); + + /* + * Verify that cwd is sane. + */ + if (stat(curdir, &sa) == -1) { + (void)fprintf(stderr, "%s: %s: %s.\n", + progname, curdir, strerror(errno)); + exit(2); + } + + /* + * All this code is so that we know where we are when we start up + * on a different machine with pmake. + * Overriding getcwd() with $PWD totally breaks MAKEOBJDIRPREFIX + * since the value of curdir can vary depending on how we got + * here. Ie sitting at a shell prompt (shell that provides $PWD) + * or via subdir.mk in which case its likely a shell which does + * not provide it. + * So, to stop it breaking this case only, we ignore PWD if + * MAKEOBJDIRPREFIX is set or MAKEOBJDIR contains a transform. + */ +#ifndef NO_PWD_OVERRIDE + if (!ignorePWD) { + char *pwd, *ptmp1 = NULL, *ptmp2 = NULL; + + if ((pwd = getenv("PWD")) != NULL && + Var_Value("MAKEOBJDIRPREFIX", VAR_CMD, &ptmp1) == NULL) { + const char *makeobjdir = Var_Value("MAKEOBJDIR", + VAR_CMD, &ptmp2); + + if (makeobjdir == NULL || !strchr(makeobjdir, '$')) { + if (stat(pwd, &sb) == 0 && + sa.st_ino == sb.st_ino && + sa.st_dev == sb.st_dev) + (void)strncpy(curdir, pwd, MAXPATHLEN); + } + } + free(ptmp1); + free(ptmp2); + } +#endif + Var_Set(".CURDIR", curdir, VAR_GLOBAL, 0); + + /* + * Find the .OBJDIR. If MAKEOBJDIRPREFIX, or failing that, + * MAKEOBJDIR is set in the environment, try only that value + * and fall back to .CURDIR if it does not exist. + * + * Otherwise, try _PATH_OBJDIR.MACHINE-MACHINE_ARCH, _PATH_OBJDIR.MACHINE, + * and * finally _PATH_OBJDIRPREFIX`pwd`, in that order. If none + * of these paths exist, just use .CURDIR. + */ + Dir_Init(curdir); + (void)Main_SetObjdir("%s", curdir); + + if (!Main_SetVarObjdir("MAKEOBJDIRPREFIX", curdir) && + !Main_SetVarObjdir("MAKEOBJDIR", "") && + !Main_SetObjdir("%s.%s-%s", _PATH_OBJDIR, machine, machine_arch) && + !Main_SetObjdir("%s.%s", _PATH_OBJDIR, machine) && + !Main_SetObjdir("%s", _PATH_OBJDIR)) + (void)Main_SetObjdir("%s%s", _PATH_OBJDIRPREFIX, curdir); + + /* + * Initialize archive, target and suffix modules in preparation for + * parsing the makefile(s) + */ + Arch_Init(); + Targ_Init(); + Suff_Init(); + Trace_Init(tracefile); + + DEFAULT = NULL; + (void)time(&now); + + Trace_Log(MAKESTART, NULL); + + /* + * Set up the .TARGETS variable to contain the list of targets to be + * created. If none specified, make the variable empty -- the parser + * will fill the thing in with the default or .MAIN target. + */ + if (!Lst_IsEmpty(create)) { + LstNode ln; + + for (ln = Lst_First(create); ln != NULL; + ln = Lst_Succ(ln)) { + char *name = (char *)Lst_Datum(ln); + + Var_Append(".TARGETS", name, VAR_GLOBAL); + } + } else + Var_Set(".TARGETS", "", VAR_GLOBAL, 0); + + + /* + * If no user-supplied system path was given (through the -m option) + * add the directories from the DEFSYSPATH (more than one may be given + * as dir1:...:dirn) to the system include path. + */ + if (syspath == NULL || *syspath == '\0') + syspath = defsyspath; + else + syspath = bmake_strdup(syspath); + + for (start = syspath; *start != '\0'; start = cp) { + for (cp = start; *cp != '\0' && *cp != ':'; cp++) + continue; + if (*cp == ':') { + *cp++ = '\0'; + } + /* look for magic parent directory search string */ + if (strncmp(".../", start, 4) != 0) { + (void)Dir_AddDir(defIncPath, start); + } else { + if (Dir_FindHereOrAbove(curdir, start+4, + found_path, sizeof(found_path))) { + (void)Dir_AddDir(defIncPath, found_path); + } + } + } + if (syspath != defsyspath) + free(syspath); + + /* + * Read in the built-in rules first, followed by the specified + * makefile, if it was (makefile != NULL), or the default + * makefile and Makefile, in that order, if it wasn't. + */ + if (!noBuiltins) { + LstNode ln; + + sysMkPath = Lst_Init(FALSE); + Dir_Expand(_PATH_DEFSYSMK, + Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath, + sysMkPath); + if (Lst_IsEmpty(sysMkPath)) + Fatal("%s: no system rules (%s).", progname, + _PATH_DEFSYSMK); + ln = Lst_Find(sysMkPath, NULL, ReadMakefile); + if (ln == NULL) + Fatal("%s: cannot open %s.", progname, + (char *)Lst_Datum(ln)); + } + + if (!Lst_IsEmpty(makefiles)) { + LstNode ln; + + ln = Lst_Find(makefiles, NULL, ReadAllMakefiles); + if (ln != NULL) + Fatal("%s: cannot open %s.", progname, + (char *)Lst_Datum(ln)); + } else { + p1 = Var_Subst(NULL, "${" MAKEFILE_PREFERENCE "}", + VAR_CMD, VARF_WANTRES); + if (p1) { + (void)str2Lst_Append(makefiles, p1, NULL); + (void)Lst_Find(makefiles, NULL, ReadMakefile); + free(p1); + } + } + + /* In particular suppress .depend for '-r -V .OBJDIR -f /dev/null' */ + if (!noBuiltins || !printVars) { + makeDependfile = Var_Subst(NULL, "${.MAKE.DEPENDFILE:T}", + VAR_CMD, VARF_WANTRES); + doing_depend = TRUE; + (void)ReadMakefile(makeDependfile, NULL); + doing_depend = FALSE; + } + + if (enterFlagObj) + printf("%s: Entering directory `%s'\n", progname, objdir); + + MakeMode(NULL); + + Var_Append("MFLAGS", Var_Value(MAKEFLAGS, VAR_GLOBAL, &p1), VAR_GLOBAL); + free(p1); + + if (!forceJobs && !compatMake && + Var_Exists(".MAKE.JOBS", VAR_GLOBAL)) { + char *value; + int n; + + value = Var_Subst(NULL, "${.MAKE.JOBS}", VAR_GLOBAL, VARF_WANTRES); + n = strtol(value, NULL, 0); + if (n < 1) { + (void)fprintf(stderr, "%s: illegal value for .MAKE.JOBS -- must be positive integer!\n", + progname); + exit(1); + } + if (n != maxJobs) { + Var_Append(MAKEFLAGS, "-j", VAR_GLOBAL); + Var_Append(MAKEFLAGS, value, VAR_GLOBAL); + } + maxJobs = n; + maxJobTokens = maxJobs; + forceJobs = TRUE; + free(value); + } + + /* + * Be compatible if user did not specify -j and did not explicitly + * turned compatibility on + */ + if (!compatMake && !forceJobs) { + compatMake = TRUE; + } + + if (!compatMake) + Job_ServerStart(maxJobTokens, jp_0, jp_1); + if (DEBUG(JOB)) + fprintf(debug_file, "job_pipe %d %d, maxjobs %d, tokens %d, compat %d\n", + jp_0, jp_1, maxJobs, maxJobTokens, compatMake); + + if (!printVars) + Main_ExportMAKEFLAGS(TRUE); /* initial export */ + + + /* + * For compatibility, look at the directories in the VPATH variable + * and add them to the search path, if the variable is defined. The + * variable's value is in the same format as the PATH envariable, i.e. + * ::... + */ + if (Var_Exists("VPATH", VAR_CMD)) { + char *vpath, savec; + /* + * GCC stores string constants in read-only memory, but + * Var_Subst will want to write this thing, so store it + * in an array + */ + static char VPATH[] = "${VPATH}"; + + vpath = Var_Subst(NULL, VPATH, VAR_CMD, VARF_WANTRES); + path = vpath; + do { + /* skip to end of directory */ + for (cp = path; *cp != ':' && *cp != '\0'; cp++) + continue; + /* Save terminator character so know when to stop */ + savec = *cp; + *cp = '\0'; + /* Add directory to search path */ + (void)Dir_AddDir(dirSearchPath, path); + *cp = savec; + path = cp + 1; + } while (savec == ':'); + free(vpath); + } + + /* + * Now that all search paths have been read for suffixes et al, it's + * time to add the default search path to their lists... + */ + Suff_DoPaths(); + + /* + * Propagate attributes through :: dependency lists. + */ + Targ_Propagate(); + + /* print the initial graph, if the user requested it */ + if (DEBUG(GRAPH1)) + Targ_PrintGraph(1); + + /* print the values of any variables requested by the user */ + if (printVars) { + doPrintVars(); + outOfDate = FALSE; + } else { + outOfDate = runTargets(); + } + +#ifdef CLEANUP + Lst_Destroy(variables, NULL); + Lst_Destroy(makefiles, NULL); + Lst_Destroy(create, (FreeProc *)free); +#endif + + /* print the graph now it's been processed if the user requested it */ + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + + Trace_Log(MAKEEND, 0); + + if (enterFlagObj) + printf("%s: Leaving directory `%s'\n", progname, objdir); + if (enterFlag) + printf("%s: Leaving directory `%s'\n", progname, curdir); + +#ifdef USE_META + meta_finish(); +#endif + Suff_End(); + Targ_End(); + Arch_End(); + Var_End(); + Parse_End(); + Dir_End(); + Job_End(); + Trace_End(); + + return outOfDate ? 1 : 0; +} + +/*- + * ReadMakefile -- + * Open and parse the given makefile. + * + * Results: + * 0 if ok. -1 if couldn't open file. + * + * Side Effects: + * lots + */ +static int +ReadMakefile(const void *p, const void *q MAKE_ATTR_UNUSED) +{ + const char *fname = p; /* makefile to read */ + int fd; + size_t len = MAXPATHLEN; + char *name, *path = bmake_malloc(len); + + if (!strcmp(fname, "-")) { + Parse_File(NULL /*stdin*/, -1); + Var_Set("MAKEFILE", "", VAR_INTERNAL, 0); + } else { + /* if we've chdir'd, rebuild the path name */ + if (strcmp(curdir, objdir) && *fname != '/') { + size_t plen = strlen(curdir) + strlen(fname) + 2; + if (len < plen) + path = bmake_realloc(path, len = 2 * plen); + + (void)snprintf(path, len, "%s/%s", curdir, fname); + fd = open(path, O_RDONLY); + if (fd != -1) { + fname = path; + goto found; + } + + /* If curdir failed, try objdir (ala .depend) */ + plen = strlen(objdir) + strlen(fname) + 2; + if (len < plen) + path = bmake_realloc(path, len = 2 * plen); + (void)snprintf(path, len, "%s/%s", objdir, fname); + fd = open(path, O_RDONLY); + if (fd != -1) { + fname = path; + goto found; + } + } else { + fd = open(fname, O_RDONLY); + if (fd != -1) + goto found; + } + /* look in -I and system include directories. */ + name = Dir_FindFile(fname, parseIncPath); + if (!name) + name = Dir_FindFile(fname, + Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); + if (!name || (fd = open(name, O_RDONLY)) == -1) { + free(name); + free(path); + return(-1); + } + fname = name; + /* + * set the MAKEFILE variable desired by System V fans -- the + * placement of the setting here means it gets set to the last + * makefile specified, as it is set by SysV make. + */ +found: + if (!doing_depend) + Var_Set("MAKEFILE", fname, VAR_INTERNAL, 0); + Parse_File(fname, fd); + } + free(path); + return(0); +} + + + +/*- + * Cmd_Exec -- + * Execute the command in cmd, and return the output of that command + * in a string. + * + * Results: + * A string containing the output of the command, or the empty string + * If errnum is not NULL, it contains the reason for the command failure + * + * Side Effects: + * The string must be freed by the caller. + */ +char * +Cmd_Exec(const char *cmd, const char **errnum) +{ + const char *args[4]; /* Args for invoking the shell */ + int fds[2]; /* Pipe streams */ + int cpid; /* Child PID */ + int pid; /* PID from wait() */ + char *res; /* result */ + int status; /* command exit status */ + Buffer buf; /* buffer to store the result */ + char *cp; + int cc; /* bytes read, or -1 */ + int savederr; /* saved errno */ + + + *errnum = NULL; + + if (!shellName) + Shell_Init(); + /* + * Set up arguments for shell + */ + args[0] = shellName; + args[1] = "-c"; + args[2] = cmd; + args[3] = NULL; + + /* + * Open a pipe for fetching its output + */ + if (pipe(fds) == -1) { + *errnum = "Couldn't create pipe for \"%s\""; + goto bad; + } + + /* + * Fork + */ + switch (cpid = vFork()) { + case 0: + /* + * Close input side of pipe + */ + (void)close(fds[0]); + + /* + * Duplicate the output stream to the shell's output, then + * shut the extra thing down. Note we don't fetch the error + * stream...why not? Why? + */ + (void)dup2(fds[1], 1); + (void)close(fds[1]); + + Var_ExportVars(); + + (void)execv(shellPath, UNCONST(args)); + _exit(1); + /*NOTREACHED*/ + + case -1: + *errnum = "Couldn't exec \"%s\""; + goto bad; + + default: + /* + * No need for the writing half + */ + (void)close(fds[1]); + + savederr = 0; + Buf_Init(&buf, 0); + + do { + char result[BUFSIZ]; + cc = read(fds[0], result, sizeof(result)); + if (cc > 0) + Buf_AddBytes(&buf, cc, result); + } + while (cc > 0 || (cc == -1 && errno == EINTR)); + if (cc == -1) + savederr = errno; + + /* + * Close the input side of the pipe. + */ + (void)close(fds[0]); + + /* + * Wait for the process to exit. + */ + while(((pid = waitpid(cpid, &status, 0)) != cpid) && (pid >= 0)) { + JobReapChild(pid, status, FALSE); + continue; + } + cc = Buf_Size(&buf); + res = Buf_Destroy(&buf, FALSE); + + if (savederr != 0) + *errnum = "Couldn't read shell's output for \"%s\""; + + if (WIFSIGNALED(status)) + *errnum = "\"%s\" exited on a signal"; + else if (WEXITSTATUS(status) != 0) + *errnum = "\"%s\" returned non-zero status"; + + /* + * Null-terminate the result, convert newlines to spaces and + * install it in the variable. + */ + res[cc] = '\0'; + cp = &res[cc]; + + if (cc > 0 && *--cp == '\n') { + /* + * A final newline is just stripped + */ + *cp-- = '\0'; + } + while (cp >= res) { + if (*cp == '\n') { + *cp = ' '; + } + cp--; + } + break; + } + return res; +bad: + res = bmake_malloc(1); + *res = '\0'; + return res; +} + +/*- + * Error -- + * Print an error message given its format. + * + * Results: + * None. + * + * Side Effects: + * The message is printed. + */ +/* VARARGS */ +void +Error(const char *fmt, ...) +{ + va_list ap; + FILE *err_file; + + err_file = debug_file; + if (err_file == stdout) + err_file = stderr; + (void)fflush(stdout); + for (;;) { + va_start(ap, fmt); + fprintf(err_file, "%s: ", progname); + (void)vfprintf(err_file, fmt, ap); + va_end(ap); + (void)fprintf(err_file, "\n"); + (void)fflush(err_file); + if (err_file == stderr) + break; + err_file = stderr; + } +} + +/*- + * Fatal -- + * Produce a Fatal error message. If jobs are running, waits for them + * to finish. + * + * Results: + * None + * + * Side Effects: + * The program exits + */ +/* VARARGS */ +void +Fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (jobsRunning) + Job_Wait(); + + (void)fflush(stdout); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + + PrintOnError(NULL, NULL); + + if (DEBUG(GRAPH2) || DEBUG(GRAPH3)) + Targ_PrintGraph(2); + Trace_Log(MAKEERROR, 0); + exit(2); /* Not 1 so -q can distinguish error */ +} + +/* + * Punt -- + * Major exception once jobs are being created. Kills all jobs, prints + * a message and exits. + * + * Results: + * None + * + * Side Effects: + * All children are killed indiscriminately and the program Lib_Exits + */ +/* VARARGS */ +void +Punt(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)fflush(stdout); + (void)fprintf(stderr, "%s: ", progname); + (void)vfprintf(stderr, fmt, ap); + va_end(ap); + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + + PrintOnError(NULL, NULL); + + DieHorribly(); +} + +/*- + * DieHorribly -- + * Exit without giving a message. + * + * Results: + * None + * + * Side Effects: + * A big one... + */ +void +DieHorribly(void) +{ + if (jobsRunning) + Job_AbortAll(); + if (DEBUG(GRAPH2)) + Targ_PrintGraph(2); + Trace_Log(MAKEERROR, 0); + exit(2); /* Not 1, so -q can distinguish error */ +} + +/* + * Finish -- + * Called when aborting due to errors in child shell to signal + * abnormal exit. + * + * Results: + * None + * + * Side Effects: + * The program exits + */ +void +Finish(int errors) + /* number of errors encountered in Make_Make */ +{ + Fatal("%d error%s", errors, errors == 1 ? "" : "s"); +} + +/* + * eunlink -- + * Remove a file carefully, avoiding directories. + */ +int +eunlink(const char *file) +{ + struct stat st; + + if (lstat(file, &st) == -1) + return -1; + + if (S_ISDIR(st.st_mode)) { + errno = EISDIR; + return -1; + } + return unlink(file); +} + +/* + * execError -- + * Print why exec failed, avoiding stdio. + */ +void +execError(const char *af, const char *av) +{ +#ifdef USE_IOVEC + int i = 0; + struct iovec iov[8]; +#define IOADD(s) \ + (void)(iov[i].iov_base = UNCONST(s), \ + iov[i].iov_len = strlen(iov[i].iov_base), \ + i++) +#else +#define IOADD(void)write(2, s, strlen(s)) +#endif + + IOADD(progname); + IOADD(": "); + IOADD(af); + IOADD("("); + IOADD(av); + IOADD(") failed ("); + IOADD(strerror(errno)); + IOADD(")\n"); + +#ifdef USE_IOVEC + while (writev(2, iov, 8) == -1 && errno == EAGAIN) + continue; +#endif +} + +/* + * usage -- + * exit with usage message + */ +static void +usage(void) +{ + char *p; + if ((p = strchr(progname, '[')) != NULL) + *p = '\0'; + + (void)fprintf(stderr, +"usage: %s [-BeikNnqrstWwX] \n\ + [-C directory] [-D variable] [-d flags] [-f makefile]\n\ + [-I directory] [-J private] [-j max_jobs] [-m directory] [-T file]\n\ + [-V variable] [-v variable] [variable=value] [target ...]\n", + progname); + exit(2); +} + +/* + * realpath(3) can get expensive, cache results... + */ +static GNode *cached_realpaths = NULL; + +static GNode * +get_cached_realpaths(void) +{ + + if (!cached_realpaths) { + cached_realpaths = Targ_NewGN("Realpath"); +#ifndef DEBUG_REALPATH_CACHE + cached_realpaths->flags = INTERNAL; +#endif + } + + return cached_realpaths; +} + +/* purge any relative paths */ +static void +purge_cached_realpaths(void) +{ + GNode *cache = get_cached_realpaths(); + Hash_Entry *he, *nhe; + Hash_Search hs; + + he = Hash_EnumFirst(&cache->context, &hs); + while (he) { + nhe = Hash_EnumNext(&hs); + if (he->name[0] != '/') { + if (DEBUG(DIR)) + fprintf(stderr, "cached_realpath: purging %s\n", he->name); + Hash_DeleteEntry(&cache->context, he); + } + he = nhe; + } +} + +char * +cached_realpath(const char *pathname, char *resolved) +{ + GNode *cache; + char *rp, *cp; + + if (!pathname || !pathname[0]) + return NULL; + + cache = get_cached_realpaths(); + + if ((rp = Var_Value(pathname, cache, &cp)) != NULL) { + /* a hit */ + strncpy(resolved, rp, MAXPATHLEN); + resolved[MAXPATHLEN - 1] = '\0'; + } else if ((rp = realpath(pathname, resolved)) != NULL) { + Var_Set(pathname, rp, cache, 0); + } /* else should we negative-cache? */ + + free(cp); + return rp ? resolved : NULL; +} + +int +PrintAddr(void *a, void *b) +{ + printf("%lx ", (unsigned long) a); + return b ? 0 : 0; +} + + +static int +addErrorCMD(void *cmdp, void *gnp) +{ + if (cmdp == NULL) + return 1; /* stop */ + Var_Append(".ERROR_CMD", cmdp, VAR_GLOBAL); + return 0; +} + +void +PrintOnError(GNode *gn, const char *s) +{ + static GNode *en = NULL; + char tmp[64]; + char *cp; + + if (s) + printf("%s", s); + + printf("\n%s: stopped in %s\n", progname, curdir); + + if (en) + return; /* we've been here! */ + if (gn) { + /* + * We can print this even if there is no .ERROR target. + */ + Var_Set(".ERROR_TARGET", gn->name, VAR_GLOBAL, 0); + Var_Delete(".ERROR_CMD", VAR_GLOBAL); + Lst_ForEach(gn->commands, addErrorCMD, gn); + } + strncpy(tmp, "${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'\n@}", + sizeof(tmp) - 1); + cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + if (cp) { + if (*cp) + printf("%s", cp); + free(cp); + } + fflush(stdout); + + /* + * Finally, see if there is a .ERROR target, and run it if so. + */ + en = Targ_FindNode(".ERROR", TARG_NOCREATE); + if (en) { + en->type |= OP_SPECIAL; + Compat_Make(en, en); + } +} + +void +Main_ExportMAKEFLAGS(Boolean first) +{ + static int once = 1; + char tmp[64]; + char *s; + + if (once != first) + return; + once = 0; + + strncpy(tmp, "${.MAKEFLAGS} ${.MAKEOVERRIDES:O:u:@v@$v=${$v:Q}@}", + sizeof(tmp)); + s = Var_Subst(NULL, tmp, VAR_CMD, VARF_WANTRES); + if (s && *s) { +#ifdef POSIX + setenv("MAKEFLAGS", s, 1); +#else + setenv("MAKE", s, 1); +#endif + } +} + +char * +getTmpdir(void) +{ + static char *tmpdir = NULL; + + if (!tmpdir) { + struct stat st; + + /* + * Honor $TMPDIR but only if it is valid. + * Ensure it ends with /. + */ + tmpdir = Var_Subst(NULL, "${TMPDIR:tA:U" _PATH_TMP "}/", VAR_GLOBAL, + VARF_WANTRES); + if (stat(tmpdir, &st) < 0 || !S_ISDIR(st.st_mode)) { + free(tmpdir); + tmpdir = bmake_strdup(_PATH_TMP); + } + } + return tmpdir; +} + +/* + * Create and open a temp file using "pattern". + * If "fnamep" is provided set it to a copy of the filename created. + * Otherwise unlink the file once open. + */ +int +mkTempFile(const char *pattern, char **fnamep) +{ + static char *tmpdir = NULL; + char tfile[MAXPATHLEN]; + int fd; + + if (!pattern) + pattern = TMPPAT; + if (!tmpdir) + tmpdir = getTmpdir(); + if (pattern[0] == '/') { + snprintf(tfile, sizeof(tfile), "%s", pattern); + } else { + snprintf(tfile, sizeof(tfile), "%s%s", tmpdir, pattern); + } + if ((fd = mkstemp(tfile)) < 0) + Punt("Could not create temporary file %s: %s", tfile, strerror(errno)); + if (fnamep) { + *fnamep = bmake_strdup(tfile); + } else { + unlink(tfile); /* we just want the descriptor */ + } + return fd; +} + +/* + * Convert a string representation of a boolean. + * Anything that looks like "No", "False", "Off", "0" etc, + * is FALSE, otherwise TRUE. + */ +Boolean +s2Boolean(const char *s, Boolean bf) +{ + if (s) { + switch(*s) { + case '\0': /* not set - the default wins */ + break; + case '0': + case 'F': + case 'f': + case 'N': + case 'n': + bf = FALSE; + break; + case 'O': + case 'o': + switch (s[1]) { + case 'F': + case 'f': + bf = FALSE; + break; + default: + bf = TRUE; + break; + } + break; + default: + bf = TRUE; + break; + } + } + return (bf); +} + +/* + * Return a Boolean based on setting of a knob. + * + * If the knob is not set, the supplied default is the return value. + * If set, anything that looks or smells like "No", "False", "Off", "0" etc, + * is FALSE, otherwise TRUE. + */ +Boolean +getBoolean(const char *name, Boolean bf) +{ + char tmp[64]; + char *cp; + + if (snprintf(tmp, sizeof(tmp), "${%s:U:tl}", name) < (int)(sizeof(tmp))) { + cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + + if (cp) { + bf = s2Boolean(cp, bf); + free(cp); + } + } + return (bf); +} diff --git a/usr.bin/make/make.1 b/usr.bin/make/make.1 new file mode 100644 index 0000000..866631c --- /dev/null +++ b/usr.bin/make/make.1 @@ -0,0 +1,2413 @@ +.\" $NetBSD: make.1,v 1.273 2018/05/27 01:14:51 christos Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)make.1 8.4 (Berkeley) 3/19/94 +.\" +.Dd May 26, 2018 +.Dt MAKE 1 +.Os +.Sh NAME +.Nm make +.Nd maintain program dependencies +.Sh SYNOPSIS +.Nm +.Op Fl BeikNnqrstWwX +.Op Fl C Ar directory +.Op Fl D Ar variable +.Op Fl d Ar flags +.Op Fl f Ar makefile +.Op Fl I Ar directory +.Op Fl J Ar private +.Op Fl j Ar max_jobs +.Op Fl m Ar directory +.Op Fl T Ar file +.Op Fl V Ar variable +.Op Fl v Ar variable +.Op Ar variable=value +.Op Ar target ... +.Sh DESCRIPTION +.Nm +is a program designed to simplify the maintenance of other programs. +Its input is a list of specifications as to the files upon which programs +and other files depend. +If no +.Fl f Ar makefile +makefile option is given, +.Nm +will try to open +.Ql Pa makefile +then +.Ql Pa Makefile +in order to find the specifications. +If the file +.Ql Pa .depend +exists, it is read (see +.Xr mkdep 1 ) . +.Pp +This manual page is intended as a reference document only. +For a more thorough description of +.Nm +and makefiles, please refer to +.%T "PMake \- A Tutorial" . +.Pp +.Nm +will prepend the contents of the +.Va MAKEFLAGS +environment variable to the command line arguments before parsing them. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl B +Try to be backwards compatible by executing a single shell per command and +by executing the commands to make the sources of a dependency line in sequence. +.It Fl C Ar directory +Change to +.Ar directory +before reading the makefiles or doing anything else. +If multiple +.Fl C +options are specified, each is interpreted relative to the previous one: +.Fl C Pa / Fl C Pa etc +is equivalent to +.Fl C Pa /etc . +.It Fl D Ar variable +Define +.Ar variable +to be 1, in the global context. +.It Fl d Ar [-]flags +Turn on debugging, and specify which portions of +.Nm +are to print debugging information. +Unless the flags are preceded by +.Ql \- +they are added to the +.Va MAKEFLAGS +environment variable and will be processed by any child make processes. +By default, debugging information is printed to standard error, +but this can be changed using the +.Ar F +debugging flag. +The debugging output is always unbuffered; in addition, if debugging +is enabled but debugging output is not directed to standard output, +then the standard output is line buffered. +.Ar Flags +is one or more of the following: +.Bl -tag -width Ds +.It Ar A +Print all possible debugging information; +equivalent to specifying all of the debugging flags. +.It Ar a +Print debugging information about archive searching and caching. +.It Ar C +Print debugging information about current working directory. +.It Ar c +Print debugging information about conditional evaluation. +.It Ar d +Print debugging information about directory searching and caching. +.It Ar e +Print debugging information about failed commands and targets. +.It Ar F Ns Oo Sy \&+ Oc Ns Ar filename +Specify where debugging output is written. +This must be the last flag, because it consumes the remainder of +the argument. +If the character immediately after the +.Ql F +flag is +.Ql \&+ , +then the file will be opened in append mode; +otherwise the file will be overwritten. +If the file name is +.Ql stdout +or +.Ql stderr +then debugging output will be written to the +standard output or standard error output file descriptors respectively +(and the +.Ql \&+ +option has no effect). +Otherwise, the output will be written to the named file. +If the file name ends +.Ql .%d +then the +.Ql %d +is replaced by the pid. +.It Ar f +Print debugging information about loop evaluation. +.It Ar "g1" +Print the input graph before making anything. +.It Ar "g2" +Print the input graph after making everything, or before exiting +on error. +.It Ar "g3" +Print the input graph before exiting on error. +.It Ar j +Print debugging information about running multiple shells. +.It Ar l +Print commands in Makefiles regardless of whether or not they are prefixed by +.Ql @ +or other "quiet" flags. +Also known as "loud" behavior. +.It Ar M +Print debugging information about "meta" mode decisions about targets. +.It Ar m +Print debugging information about making targets, including modification +dates. +.It Ar n +Don't delete the temporary command scripts created when running commands. +These temporary scripts are created in the directory +referred to by the +.Ev TMPDIR +environment variable, or in +.Pa /tmp +if +.Ev TMPDIR +is unset or set to the empty string. +The temporary scripts are created by +.Xr mkstemp 3 , +and have names of the form +.Pa makeXXXXXX . +.Em NOTE : +This can create many files in +.Ev TMPDIR +or +.Pa /tmp , +so use with care. +.It Ar p +Print debugging information about makefile parsing. +.It Ar s +Print debugging information about suffix-transformation rules. +.It Ar t +Print debugging information about target list maintenance. +.It Ar V +Force the +.Fl V +option to print raw values of variables, overriding the default behavior +set via +.Va .MAKE.EXPAND_VARIABLES . +.It Ar v +Print debugging information about variable assignment. +.It Ar x +Run shell commands with +.Fl x +so the actual commands are printed as they are executed. +.El +.It Fl e +Specify that environment variables override macro assignments within +makefiles. +.It Fl f Ar makefile +Specify a makefile to read instead of the default +.Ql Pa makefile . +If +.Ar makefile +is +.Ql Fl , +standard input is read. +Multiple makefiles may be specified, and are read in the order specified. +.It Fl I Ar directory +Specify a directory in which to search for makefiles and included makefiles. +The system makefile directory (or directories, see the +.Fl m +option) is automatically included as part of this list. +.It Fl i +Ignore non-zero exit of shell commands in the makefile. +Equivalent to specifying +.Ql Fl +before each command line in the makefile. +.It Fl J Ar private +This option should +.Em not +be specified by the user. +.Pp +When the +.Ar j +option is in use in a recursive build, this option is passed by a make +to child makes to allow all the make processes in the build to +cooperate to avoid overloading the system. +.It Fl j Ar max_jobs +Specify the maximum number of jobs that +.Nm +may have running at any one time. +The value is saved in +.Va .MAKE.JOBS . +Turns compatibility mode off, unless the +.Ar B +flag is also specified. +When compatibility mode is off, all commands associated with a +target are executed in a single shell invocation as opposed to the +traditional one shell invocation per line. +This can break traditional scripts which change directories on each +command invocation and then expect to start with a fresh environment +on the next line. +It is more efficient to correct the scripts rather than turn backwards +compatibility on. +.It Fl k +Continue processing after errors are encountered, but only on those targets +that do not depend on the target whose creation caused the error. +.It Fl m Ar directory +Specify a directory in which to search for sys.mk and makefiles included +via the +.Ao Ar file Ac Ns -style +include statement. +The +.Fl m +option can be used multiple times to form a search path. +This path will override the default system include path: /usr/share/mk. +Furthermore the system include path will be appended to the search path used +for +.Qo Ar file Qc Ns -style +include statements (see the +.Fl I +option). +.Pp +If a file or directory name in the +.Fl m +argument (or the +.Ev MAKESYSPATH +environment variable) starts with the string +.Qq \&.../ +then +.Nm +will search for the specified file or directory named in the remaining part +of the argument string. +The search starts with the current directory of +the Makefile and then works upward towards the root of the file system. +If the search is successful, then the resulting directory replaces the +.Qq \&.../ +specification in the +.Fl m +argument. +If used, this feature allows +.Nm +to easily search in the current source tree for customized sys.mk files +(e.g., by using +.Qq \&.../mk/sys.mk +as an argument). +.It Fl n +Display the commands that would have been executed, but do not +actually execute them unless the target depends on the .MAKE special +source (see below). +.It Fl N +Display the commands which would have been executed, but do not +actually execute any of them; useful for debugging top-level makefiles +without descending into subdirectories. +.It Fl q +Do not execute any commands, but exit 0 if the specified targets are +up-to-date and 1, otherwise. +.It Fl r +Do not use the built-in rules specified in the system makefile. +.It Fl s +Do not echo any commands as they are executed. +Equivalent to specifying +.Ql Ic @ +before each command line in the makefile. +.It Fl T Ar tracefile +When used with the +.Fl j +flag, +append a trace record to +.Ar tracefile +for each job started and completed. +.It Fl t +Rather than re-building a target as specified in the makefile, create it +or update its modification time to make it appear up-to-date. +.It Fl V Ar variable +Print the value of +.Ar variable . +Do not build any targets. +Multiple instances of this option may be specified; +the variables will be printed one per line, +with a blank line for each null or undefined variable. +The value printed is extracted from the global context after all +makefiles have been read. +By default, the raw variable contents (which may +include additional unexpanded variable references) are shown. +If +.Ar variable +contains a +.Ql \&$ +then the value will be recursively expanded to its complete resultant +text before printing. +The expanded value will also be printed if +.Va .MAKE.EXPAND_VARIABLES +is set to true and +the +.Fl dV +option has not been used to override it. +Note that loop-local and target-local variables, as well as values +taken temporarily by global variables during makefile processing, are +not accessible via this option. +The +.Fl dv +debug mode can be used to see these at the cost of generating +substantial extraneous output. +.It Fl v Ar variable +Like +.Fl V +but the variable is always expanded to its complete value. +.It Fl W +Treat any warnings during makefile parsing as errors. +.It Fl w +Print entering and leaving directory messages, pre and post processing. +.It Fl X +Don't export variables passed on the command line to the environment +individually. +Variables passed on the command line are still exported +via the +.Va MAKEFLAGS +environment variable. +This option may be useful on systems which have a small limit on the +size of command arguments. +.It Ar variable=value +Set the value of the variable +.Ar variable +to +.Ar value . +Normally, all values passed on the command line are also exported to +sub-makes in the environment. +The +.Fl X +flag disables this behavior. +Variable assignments should follow options for POSIX compatibility +but no ordering is enforced. +.El +.Pp +There are seven different types of lines in a makefile: file dependency +specifications, shell commands, variable assignments, include statements, +conditional directives, for loops, and comments. +.Pp +In general, lines may be continued from one line to the next by ending +them with a backslash +.Pq Ql \e . +The trailing newline character and initial whitespace on the following +line are compressed into a single space. +.Sh FILE DEPENDENCY SPECIFICATIONS +Dependency lines consist of one or more targets, an operator, and zero +or more sources. +This creates a relationship where the targets +.Dq depend +on the sources +and are usually created from them. +The exact relationship between the target and the source is determined +by the operator that separates them. +The three operators are as follows: +.Bl -tag -width flag +.It Ic \&: +A target is considered out-of-date if its modification time is less than +those of any of its sources. +Sources for a target accumulate over dependency lines when this operator +is used. +The target is removed if +.Nm +is interrupted. +.It Ic \&! +Targets are always re-created, but not until all sources have been +examined and re-created as necessary. +Sources for a target accumulate over dependency lines when this operator +is used. +The target is removed if +.Nm +is interrupted. +.It Ic \&:: +If no sources are specified, the target is always re-created. +Otherwise, a target is considered out-of-date if any of its sources has +been modified more recently than the target. +Sources for a target do not accumulate over dependency lines when this +operator is used. +The target will not be removed if +.Nm +is interrupted. +.El +.Pp +Targets and sources may contain the shell wildcard values +.Ql \&? , +.Ql * , +.Ql [] , +and +.Ql {} . +The values +.Ql \&? , +.Ql * , +and +.Ql [] +may only be used as part of the final +component of the target or source, and must be used to describe existing +files. +The value +.Ql {} +need not necessarily be used to describe existing files. +Expansion is in directory order, not alphabetically as done in the shell. +.Sh SHELL COMMANDS +Each target may have associated with it one or more lines of shell +commands, normally +used to create the target. +Each of the lines in this script +.Em must +be preceded by a tab. +(For historical reasons, spaces are not accepted.) +While targets can appear in many dependency lines if desired, by +default only one of these rules may be followed by a creation +script. +If the +.Ql Ic \&:: +operator is used, however, all rules may include scripts and the +scripts are executed in the order found. +.Pp +Each line is treated as a separate shell command, unless the end of +line is escaped with a backslash +.Pq Ql \e +in which case that line and the next are combined. +.\" The escaped newline is retained and passed to the shell, which +.\" normally ignores it. +.\" However, the tab at the beginning of the following line is removed. +If the first characters of the command are any combination of +.Ql Ic @ , +.Ql Ic + , +or +.Ql Ic \- , +the command is treated specially. +A +.Ql Ic @ +causes the command not to be echoed before it is executed. +A +.Ql Ic + +causes the command to be executed even when +.Fl n +is given. +This is similar to the effect of the .MAKE special source, +except that the effect can be limited to a single line of a script. +A +.Ql Ic \- +in compatibility mode +causes any non-zero exit status of the command line to be ignored. +.Pp +When +.Nm +is run in jobs mode with +.Fl j Ar max_jobs , +the entire script for the target is fed to a +single instance of the shell. +In compatibility (non-jobs) mode, each command is run in a separate process. +If the command contains any shell meta characters +.Pq Ql #=|^(){};&<>*?[]:$`\e\en +it will be passed to the shell; otherwise +.Nm +will attempt direct execution. +If a line starts with +.Ql Ic \- +and the shell has ErrCtl enabled then failure of the command line +will be ignored as in compatibility mode. +Otherwise +.Ql Ic \- +affects the entire job; +the script will stop at the first command line that fails, +but the target will not be deemed to have failed. +.Pp +Makefiles should be written so that the mode of +.Nm +operation does not change their behavior. +For example, any command which needs to use +.Dq cd +or +.Dq chdir +without potentially changing the directory for subsequent commands +should be put in parentheses so it executes in a subshell. +To force the use of one shell, escape the line breaks so as to make +the whole script one command. +For example: +.Bd -literal -offset indent +avoid-chdir-side-effects: + @echo Building $@ in `pwd` + @(cd ${.CURDIR} && ${MAKE} $@) + @echo Back in `pwd` + +ensure-one-shell-regardless-of-mode: + @echo Building $@ in `pwd`; \e + (cd ${.CURDIR} && ${MAKE} $@); \e + echo Back in `pwd` +.Ed +.Pp +Since +.Nm +will +.Xr chdir 2 +to +.Ql Va .OBJDIR +before executing any targets, each child process +starts with that as its current working directory. +.Sh VARIABLE ASSIGNMENTS +Variables in make are much like variables in the shell, and, by tradition, +consist of all upper-case letters. +.Ss Variable assignment modifiers +The five operators that can be used to assign values to variables are as +follows: +.Bl -tag -width Ds +.It Ic \&= +Assign the value to the variable. +Any previous value is overridden. +.It Ic \&+= +Append the value to the current value of the variable. +.It Ic \&?= +Assign the value to the variable if it is not already defined. +.It Ic \&:= +Assign with expansion, i.e. expand the value before assigning it +to the variable. +Normally, expansion is not done until the variable is referenced. +.Em NOTE : +References to undefined variables are +.Em not +expanded. +This can cause problems when variable modifiers are used. +.It Ic \&!= +Expand the value and pass it to the shell for execution and assign +the result to the variable. +Any newlines in the result are replaced with spaces. +.El +.Pp +Any white-space before the assigned +.Ar value +is removed; if the value is being appended, a single space is inserted +between the previous contents of the variable and the appended value. +.Pp +Variables are expanded by surrounding the variable name with either +curly braces +.Pq Ql {} +or parentheses +.Pq Ql () +and preceding it with +a dollar sign +.Pq Ql \&$ . +If the variable name contains only a single letter, the surrounding +braces or parentheses are not required. +This shorter form is not recommended. +.Pp +If the variable name contains a dollar, then the name itself is expanded first. +This allows almost arbitrary variable names, however names containing dollar, +braces, parenthesis, or whitespace are really best avoided! +.Pp +If the result of expanding a variable contains a dollar sign +.Pq Ql \&$ +the string is expanded again. +.Pp +Variable substitution occurs at three distinct times, depending on where +the variable is being used. +.Bl -enum +.It +Variables in dependency lines are expanded as the line is read. +.It +Variables in shell commands are expanded when the shell command is +executed. +.It +.Dq .for +loop index variables are expanded on each loop iteration. +Note that other variables are not expanded inside loops so +the following example code: +.Bd -literal -offset indent + +.Dv .for i in 1 2 3 +a+= ${i} +j= ${i} +b+= ${j} +.Dv .endfor + +all: + @echo ${a} + @echo ${b} + +.Ed +will print: +.Bd -literal -offset indent +1 2 3 +3 3 3 + +.Ed +Because while ${a} contains +.Dq 1 2 3 +after the loop is executed, ${b} +contains +.Dq ${j} ${j} ${j} +which expands to +.Dq 3 3 3 +since after the loop completes ${j} contains +.Dq 3 . +.El +.Ss Variable classes +The four different classes of variables (in order of increasing precedence) +are: +.Bl -tag -width Ds +.It Environment variables +Variables defined as part of +.Nm Ns 's +environment. +.It Global variables +Variables defined in the makefile or in included makefiles. +.It Command line variables +Variables defined as part of the command line. +.It Local variables +Variables that are defined specific to a certain target. +.El +.Pp +Local variables are all built in and their values vary magically from +target to target. +It is not currently possible to define new local variables. +The seven local variables are as follows: +.Bl -tag -width ".ARCHIVE" -offset indent +.It Va .ALLSRC +The list of all sources for this target; also known as +.Ql Va \&> . +.It Va .ARCHIVE +The name of the archive file; also known as +.Ql Va \&! . +.It Va .IMPSRC +In suffix-transformation rules, the name/path of the source from which the +target is to be transformed (the +.Dq implied +source); also known as +.Ql Va \&< . +It is not defined in explicit rules. +.It Va .MEMBER +The name of the archive member; also known as +.Ql Va % . +.It Va .OODATE +The list of sources for this target that were deemed out-of-date; also +known as +.Ql Va \&? . +.It Va .PREFIX +The file prefix of the target, containing only the file portion, no suffix +or preceding directory components; also known as +.Ql Va * . +The suffix must be one of the known suffixes declared with +.Ic .SUFFIXES +or it will not be recognized. +.It Va .TARGET +The name of the target; also known as +.Ql Va @ . +For compatibility with other makes this is an alias for +.Ic .ARCHIVE +in archive member rules. +.El +.Pp +The shorter forms +.Ql ( Va > , +.Ql Va \&! , +.Ql Va < , +.Ql Va % , +.Ql Va \&? , +.Ql Va * , +and +.Ql Va @ ) +are permitted for backward +compatibility with historical makefiles and legacy POSIX make and are +not recommended. +.Pp +Variants of these variables with the punctuation followed immediately by +.Ql D +or +.Ql F , +e.g. +.Ql Va $(@D) , +are legacy forms equivalent to using the +.Ql :H +and +.Ql :T +modifiers. +These forms are accepted for compatibility with +.At V +makefiles and POSIX but are not recommended. +.Pp +Four of the local variables may be used in sources on dependency lines +because they expand to the proper value for each target on the line. +These variables are +.Ql Va .TARGET , +.Ql Va .PREFIX , +.Ql Va .ARCHIVE , +and +.Ql Va .MEMBER . +.Ss Additional built-in variables +In addition, +.Nm +sets or knows about the following variables: +.Bl -tag -width .MAKEOVERRIDES +.It Va \&$ +A single dollar sign +.Ql \&$ , +i.e. +.Ql \&$$ +expands to a single dollar +sign. +.It Va .ALLTARGETS +The list of all targets encountered in the Makefile. +If evaluated during +Makefile parsing, lists only those targets encountered thus far. +.It Va .CURDIR +A path to the directory where +.Nm +was executed. +Refer to the description of +.Ql Ev PWD +for more details. +.It Va .INCLUDEDFROMDIR +The directory of the file this Makefile was included from. +.It Va .INCLUDEDFROMFILE +The filename of the file this Makefile was included from. +.It Ev MAKE +The name that +.Nm +was executed with +.Pq Va argv[0] . +For compatibility +.Nm +also sets +.Va .MAKE +with the same value. +The preferred variable to use is the environment variable +.Ev MAKE +because it is more compatible with other versions of +.Nm +and cannot be confused with the special target with the same name. +.It Va .MAKE.DEPENDFILE +Names the makefile (default +.Ql Pa .depend ) +from which generated dependencies are read. +.It Va .MAKE.EXPAND_VARIABLES +A boolean that controls the default behavior of the +.Fl V +option. +If true, variable values printed with +.Fl V +are fully expanded; if false, the raw variable contents (which may +include additional unexpanded variable references) are shown. +.It Va .MAKE.EXPORTED +The list of variables exported by +.Nm . +.It Va .MAKE.JOBS +The argument to the +.Fl j +option. +.It Va .MAKE.JOB.PREFIX +If +.Nm +is run with +.Ar j +then output for each target is prefixed with a token +.Ql --- target --- +the first part of which can be controlled via +.Va .MAKE.JOB.PREFIX . +If +.Va .MAKE.JOB.PREFIX +is empty, no token is printed. +.br +For example: +.Li .MAKE.JOB.PREFIX=${.newline}---${.MAKE:T}[${.MAKE.PID}] +would produce tokens like +.Ql ---make[1234] target --- +making it easier to track the degree of parallelism being achieved. +.It Ev MAKEFLAGS +The environment variable +.Ql Ev MAKEFLAGS +may contain anything that +may be specified on +.Nm Ns 's +command line. +Anything specified on +.Nm Ns 's +command line is appended to the +.Ql Ev MAKEFLAGS +variable which is then +entered into the environment for all programs which +.Nm +executes. +.It Va .MAKE.LEVEL +The recursion depth of +.Nm . +The initial instance of +.Nm +will be 0, and an incremented value is put into the environment +to be seen by the next generation. +This allows tests like: +.Li .if ${.MAKE.LEVEL} == 0 +to protect things which should only be evaluated in the initial instance of +.Nm . +.It Va .MAKE.MAKEFILE_PREFERENCE +The ordered list of makefile names +(default +.Ql Pa makefile , +.Ql Pa Makefile ) +that +.Nm +will look for. +.It Va .MAKE.MAKEFILES +The list of makefiles read by +.Nm , +which is useful for tracking dependencies. +Each makefile is recorded only once, regardless of the number of times read. +.It Va .MAKE.MODE +Processed after reading all makefiles. +Can affect the mode that +.Nm +runs in. +It can contain a number of keywords: +.Bl -hang -width missing-filemon=bf. +.It Pa compat +Like +.Fl B , +puts +.Nm +into "compat" mode. +.It Pa meta +Puts +.Nm +into "meta" mode, where meta files are created for each target +to capture the command run, the output generated and if +.Xr filemon 4 +is available, the system calls which are of interest to +.Nm . +The captured output can be very useful when diagnosing errors. +.It Pa curdirOk= Ar bf +Normally +.Nm +will not create .meta files in +.Ql Va .CURDIR . +This can be overridden by setting +.Va bf +to a value which represents True. +.It Pa missing-meta= Ar bf +If +.Va bf +is True, then a missing .meta file makes the target out-of-date. +.It Pa missing-filemon= Ar bf +If +.Va bf +is True, then missing filemon data makes the target out-of-date. +.It Pa nofilemon +Do not use +.Xr filemon 4 . +.It Pa env +For debugging, it can be useful to include the environment +in the .meta file. +.It Pa verbose +If in "meta" mode, print a clue about the target being built. +This is useful if the build is otherwise running silently. +The message printed the value of: +.Va .MAKE.META.PREFIX . +.It Pa ignore-cmd +Some makefiles have commands which are simply not stable. +This keyword causes them to be ignored for +determining whether a target is out of date in "meta" mode. +See also +.Ic .NOMETA_CMP . +.It Pa silent= Ar bf +If +.Va bf +is True, when a .meta file is created, mark the target +.Ic .SILENT . +.El +.It Va .MAKE.META.BAILIWICK +In "meta" mode, provides a list of prefixes which +match the directories controlled by +.Nm . +If a file that was generated outside of +.Va .OBJDIR +but within said bailiwick is missing, +the current target is considered out-of-date. +.It Va .MAKE.META.CREATED +In "meta" mode, this variable contains a list of all the meta files +updated. +If not empty, it can be used to trigger processing of +.Va .MAKE.META.FILES . +.It Va .MAKE.META.FILES +In "meta" mode, this variable contains a list of all the meta files +used (updated or not). +This list can be used to process the meta files to extract dependency +information. +.It Va .MAKE.META.IGNORE_PATHS +Provides a list of path prefixes that should be ignored; +because the contents are expected to change over time. +The default list includes: +.Ql Pa /dev /etc /proc /tmp /var/run /var/tmp +.It Va .MAKE.META.IGNORE_PATTERNS +Provides a list of patterns to match against pathnames. +Ignore any that match. +.It Va .MAKE.META.IGNORE_FILTER +Provides a list of variable modifiers to apply to each pathname. +Ignore if the expansion is an empty string. +.It Va .MAKE.META.PREFIX +Defines the message printed for each meta file updated in "meta verbose" mode. +The default value is: +.Dl Building ${.TARGET:H:tA}/${.TARGET:T} +.It Va .MAKEOVERRIDES +This variable is used to record the names of variables assigned to +on the command line, so that they may be exported as part of +.Ql Ev MAKEFLAGS . +This behavior can be disabled by assigning an empty value to +.Ql Va .MAKEOVERRIDES +within a makefile. +Extra variables can be exported from a makefile +by appending their names to +.Ql Va .MAKEOVERRIDES . +.Ql Ev MAKEFLAGS +is re-exported whenever +.Ql Va .MAKEOVERRIDES +is modified. +.It Va .MAKE.PATH_FILEMON +If +.Nm +was built with +.Xr filemon 4 +support, this is set to the path of the device node. +This allows makefiles to test for this support. +.It Va .MAKE.PID +The process-id of +.Nm . +.It Va .MAKE.PPID +The parent process-id of +.Nm . +.It Va .MAKE.SAVE_DOLLARS +value should be a boolean that controls whether +.Ql $$ +are preserved when doing +.Ql := +assignments. +The default is true, for compatibility with other makes. +If set to false, +.Ql $$ +becomes +.Ql $ +per normal evaluation rules. +.It Va MAKE_PRINT_VAR_ON_ERROR +When +.Nm +stops due to an error, it sets +.Ql Va .ERROR_TARGET +to the name of the target that failed, +.Ql Va .ERROR_CMD +to the commands of the failed target, +and in "meta" mode, it also sets +.Ql Va .ERROR_CWD +to the +.Xr getcwd 3 , +and +.Ql Va .ERROR_META_FILE +to the path of the meta file (if any) describing the failed target. +It then prints its name and the value of +.Ql Va .CURDIR +as well as the value of any variables named in +.Ql Va MAKE_PRINT_VAR_ON_ERROR . +.It Va .newline +This variable is simply assigned a newline character as its value. +This allows expansions using the +.Cm \&:@ +modifier to put a newline between +iterations of the loop rather than a space. +For example, the printing of +.Ql Va MAKE_PRINT_VAR_ON_ERROR +could be done as ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@}. +.It Va .OBJDIR +A path to the directory where the targets are built. +Its value is determined by trying to +.Xr chdir 2 +to the following directories in order and using the first match: +.Bl -enum +.It +.Ev ${MAKEOBJDIRPREFIX}${.CURDIR} +.Pp +(Only if +.Ql Ev MAKEOBJDIRPREFIX +is set in the environment or on the command line.) +.It +.Ev ${MAKEOBJDIR} +.Pp +(Only if +.Ql Ev MAKEOBJDIR +is set in the environment or on the command line.) +.It +.Ev ${.CURDIR} Ns Pa /obj. Ns Ev ${MACHINE} +.It +.Ev ${.CURDIR} Ns Pa /obj +.It +.Pa /usr/obj/ Ns Ev ${.CURDIR} +.It +.Ev ${.CURDIR} +.El +.Pp +Variable expansion is performed on the value before it's used, +so expressions such as +.Dl ${.CURDIR:S,^/usr/src,/var/obj,} +may be used. +This is especially useful with +.Ql Ev MAKEOBJDIR . +.Pp +.Ql Va .OBJDIR +may be modified in the makefile via the special target +.Ql Ic .OBJDIR . +In all cases, +.Nm +will +.Xr chdir 2 +to the specified directory if it exists, and set +.Ql Va .OBJDIR +and +.Ql Ev PWD +to that directory before executing any targets. +. +.It Va .PARSEDIR +A path to the directory of the current +.Ql Pa Makefile +being parsed. +.It Va .PARSEFILE +The basename of the current +.Ql Pa Makefile +being parsed. +This variable and +.Ql Va .PARSEDIR +are both set only while the +.Ql Pa Makefiles +are being parsed. +If you want to retain their current values, assign them to a variable +using assignment with expansion: +.Pq Ql Cm \&:= . +.It Va .PATH +A variable that represents the list of directories that +.Nm +will search for files. +The search list should be updated using the target +.Ql Va .PATH +rather than the variable. +.It Ev PWD +Alternate path to the current directory. +.Nm +normally sets +.Ql Va .CURDIR +to the canonical path given by +.Xr getcwd 3 . +However, if the environment variable +.Ql Ev PWD +is set and gives a path to the current directory, then +.Nm +sets +.Ql Va .CURDIR +to the value of +.Ql Ev PWD +instead. +This behavior is disabled if +.Ql Ev MAKEOBJDIRPREFIX +is set or +.Ql Ev MAKEOBJDIR +contains a variable transform. +.Ql Ev PWD +is set to the value of +.Ql Va .OBJDIR +for all programs which +.Nm +executes. +.It Ev .TARGETS +The list of targets explicitly specified on the command line, if any. +.It Ev VPATH +Colon-separated +.Pq Dq \&: +lists of directories that +.Nm +will search for files. +The variable is supported for compatibility with old make programs only, +use +.Ql Va .PATH +instead. +.El +.Ss Variable modifiers +Variable expansion may be modified to select or modify each word of the +variable (where a +.Dq word +is white-space delimited sequence of characters). +The general format of a variable expansion is as follows: +.Pp +.Dl ${variable[:modifier[:...]]} +.Pp +Each modifier begins with a colon, +which may be escaped with a backslash +.Pq Ql \e . +.Pp +A set of modifiers can be specified via a variable, as follows: +.Pp +.Dl modifier_variable=modifier[:...] +.Dl ${variable:${modifier_variable}[:...]} +.Pp +In this case the first modifier in the modifier_variable does not +start with a colon, since that must appear in the referencing +variable. +If any of the modifiers in the modifier_variable contain a dollar sign +.Pq Ql $ , +these must be doubled to avoid early expansion. +.Pp +The supported modifiers are: +.Bl -tag -width EEE +.It Cm \&:E +Replaces each word in the variable with its suffix. +.It Cm \&:H +Replaces each word in the variable with everything but the last component. +.It Cm \&:M Ns Ar pattern +Select only those words that match +.Ar pattern . +The standard shell wildcard characters +.Pf ( Ql * , +.Ql \&? , +and +.Ql Oo Oc ) +may +be used. +The wildcard characters may be escaped with a backslash +.Pq Ql \e . +As a consequence of the way values are split into words, matched, +and then joined, a construct like +.Dl ${VAR:M*} +will normalize the inter-word spacing, removing all leading and +trailing space, and converting multiple consecutive spaces +to single spaces. +. +.It Cm \&:N Ns Ar pattern +This is identical to +.Ql Cm \&:M , +but selects all words which do not match +.Ar pattern . +.It Cm \&:O +Order every word in variable alphabetically. +To sort words in +reverse order use the +.Ql Cm \&:O:[-1..1] +combination of modifiers. +.It Cm \&:Ox +Randomize words in variable. +The results will be different each time you are referring to the +modified variable; use the assignment with expansion +.Pq Ql Cm \&:= +to prevent such behavior. +For example, +.Bd -literal -offset indent +LIST= uno due tre quattro +RANDOM_LIST= ${LIST:Ox} +STATIC_RANDOM_LIST:= ${LIST:Ox} + +all: + @echo "${RANDOM_LIST}" + @echo "${RANDOM_LIST}" + @echo "${STATIC_RANDOM_LIST}" + @echo "${STATIC_RANDOM_LIST}" +.Ed +may produce output similar to: +.Bd -literal -offset indent +quattro due tre uno +tre due quattro uno +due uno quattro tre +due uno quattro tre +.Ed +.It Cm \&:Q +Quotes every shell meta-character in the variable, so that it can be passed +safely to the shell. +.It Cm \&:q +Quotes every shell meta-character in the variable, and also doubles +.Sq $ +characters so that it can be passed +safely through recursive invocations of +.Nm . +This is equivalent to: +.Sq \&:S/\e\&$/&&/g:Q . +.It Cm \&:R +Replaces each word in the variable with everything but its suffix. +.It Cm \&:range[=count] +The value is an integer sequence representing the words of the original +value, or the supplied +.Va count . +.It Cm \&:gmtime[=utc] +The value is a format string for +.Xr strftime 3 , +using +.Xr gmtime 3 . +If a +.Va utc +value is not provided or is 0, the current time is used. +.It Cm \&:hash +Compute a 32-bit hash of the value and encode it as hex digits. +.It Cm \&:localtime[=utc] +The value is a format string for +.Xr strftime 3 , +using +.Xr localtime 3 . +If a +.Va utc +value is not provided or is 0, the current time is used. +.It Cm \&:tA +Attempt to convert variable to an absolute path using +.Xr realpath 3 , +if that fails, the value is unchanged. +.It Cm \&:tl +Converts variable to lower-case letters. +.It Cm \&:ts Ns Ar c +Words in the variable are normally separated by a space on expansion. +This modifier sets the separator to the character +.Ar c . +If +.Ar c +is omitted, then no separator is used. +The common escapes (including octal numeric codes), work as expected. +.It Cm \&:tu +Converts variable to upper-case letters. +.It Cm \&:tW +Causes the value to be treated as a single word +(possibly containing embedded white space). +See also +.Ql Cm \&:[*] . +.It Cm \&:tw +Causes the value to be treated as a sequence of +words delimited by white space. +See also +.Ql Cm \&:[@] . +.Sm off +.It Cm \&:S No \&/ Ar old_string No \&/ Ar new_string No \&/ Op Cm 1gW +.Sm on +Modify the first occurrence of +.Ar old_string +in the variable's value, replacing it with +.Ar new_string . +If a +.Ql g +is appended to the last slash of the pattern, all occurrences +in each word are replaced. +If a +.Ql 1 +is appended to the last slash of the pattern, only the first word +is affected. +If a +.Ql W +is appended to the last slash of the pattern, +then the value is treated as a single word +(possibly containing embedded white space). +If +.Ar old_string +begins with a caret +.Pq Ql ^ , +.Ar old_string +is anchored at the beginning of each word. +If +.Ar old_string +ends with a dollar sign +.Pq Ql \&$ , +it is anchored at the end of each word. +Inside +.Ar new_string , +an ampersand +.Pq Ql & +is replaced by +.Ar old_string +(without any +.Ql ^ +or +.Ql \&$ ) . +Any character may be used as a delimiter for the parts of the modifier +string. +The anchoring, ampersand and delimiter characters may be escaped with a +backslash +.Pq Ql \e . +.Pp +Variable expansion occurs in the normal fashion inside both +.Ar old_string +and +.Ar new_string +with the single exception that a backslash is used to prevent the expansion +of a dollar sign +.Pq Ql \&$ , +not a preceding dollar sign as is usual. +.Sm off +.It Cm \&:C No \&/ Ar pattern No \&/ Ar replacement No \&/ Op Cm 1gW +.Sm on +The +.Cm \&:C +modifier is just like the +.Cm \&:S +modifier except that the old and new strings, instead of being +simple strings, are an extended regular expression (see +.Xr regex 3 ) +string +.Ar pattern +and an +.Xr ed 1 Ns \-style +string +.Ar replacement . +Normally, the first occurrence of the pattern +.Ar pattern +in each word of the value is substituted with +.Ar replacement . +The +.Ql 1 +modifier causes the substitution to apply to at most one word; the +.Ql g +modifier causes the substitution to apply to as many instances of the +search pattern +.Ar pattern +as occur in the word or words it is found in; the +.Ql W +modifier causes the value to be treated as a single word +(possibly containing embedded white space). +Note that +.Ql 1 +and +.Ql g +are orthogonal; the former specifies whether multiple words are +potentially affected, the latter whether multiple substitutions can +potentially occur within each affected word. +.Pp +As for the +.Cm \&:S +modifier, the +.Ar pattern +and +.Ar replacement +are subjected to variable expansion before being parsed as +regular expressions. +.It Cm \&:T +Replaces each word in the variable with its last component. +.It Cm \&:u +Remove adjacent duplicate words (like +.Xr uniq 1 ) . +.Sm off +.It Cm \&:\&? Ar true_string Cm \&: Ar false_string +.Sm on +If the variable name (not its value), when parsed as a .if conditional +expression, evaluates to true, return as its value the +.Ar true_string , +otherwise return the +.Ar false_string . +Since the variable name is used as the expression, \&:\&? must be the +first modifier after the variable name itself - which will, of course, +usually contain variable expansions. +A common error is trying to use expressions like +.Dl ${NUMBERS:M42:?match:no} +which actually tests defined(NUMBERS), +to determine is any words match "42" you need to use something like: +.Dl ${"${NUMBERS:M42}" != \&"\&":?match:no} . +.It Ar :old_string=new_string +This is the +.At V +style variable substitution. +It must be the last modifier specified. +If +.Ar old_string +or +.Ar new_string +do not contain the pattern matching character +.Ar % +then it is assumed that they are +anchored at the end of each word, so only suffixes or entire +words may be replaced. +Otherwise +.Ar % +is the substring of +.Ar old_string +to be replaced in +.Ar new_string . +.Pp +Variable expansion occurs in the normal fashion inside both +.Ar old_string +and +.Ar new_string +with the single exception that a backslash is used to prevent the +expansion of a dollar sign +.Pq Ql \&$ , +not a preceding dollar sign as is usual. +.Sm off +.It Cm \&:@ Ar temp Cm @ Ar string Cm @ +.Sm on +This is the loop expansion mechanism from the OSF Development +Environment (ODE) make. +Unlike +.Cm \&.for +loops expansion occurs at the time of +reference. +Assign +.Ar temp +to each word in the variable and evaluate +.Ar string . +The ODE convention is that +.Ar temp +should start and end with a period. +For example. +.Dl ${LINKS:@.LINK.@${LN} ${TARGET} ${.LINK.}@} +.Pp +However a single character variable is often more readable: +.Dl ${MAKE_PRINT_VAR_ON_ERROR:@v@$v='${$v}'${.newline}@} +.It Cm \&:_[=var] +Save the current variable value in +.Ql $_ +or the named +.Va var +for later reference. +Example usage: +.Bd -literal -offset indent +M_cmpv.units = 1 1000 1000000 +M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \&\\ +\\* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh + +.Dv .if ${VERSION:${M_cmpv}} < ${3.1.12:L:${M_cmpv}} + +.Ed +Here +.Ql $_ +is used to save the result of the +.Ql :S +modifier which is later referenced using the index values from +.Ql :range . +.It Cm \&:U Ns Ar newval +If the variable is undefined +.Ar newval +is the value. +If the variable is defined, the existing value is returned. +This is another ODE make feature. +It is handy for setting per-target CFLAGS for instance: +.Dl ${_${.TARGET:T}_CFLAGS:U${DEF_CFLAGS}} +If a value is only required if the variable is undefined, use: +.Dl ${VAR:D:Unewval} +.It Cm \&:D Ns Ar newval +If the variable is defined +.Ar newval +is the value. +.It Cm \&:L +The name of the variable is the value. +.It Cm \&:P +The path of the node which has the same name as the variable +is the value. +If no such node exists or its path is null, then the +name of the variable is used. +In order for this modifier to work, the name (node) must at least have +appeared on the rhs of a dependency. +.Sm off +.It Cm \&:\&! Ar cmd Cm \&! +.Sm on +The output of running +.Ar cmd +is the value. +.It Cm \&:sh +If the variable is non-empty it is run as a command and the output +becomes the new value. +.It Cm \&::= Ns Ar str +The variable is assigned the value +.Ar str +after substitution. +This modifier and its variations are useful in +obscure situations such as wanting to set a variable when shell commands +are being parsed. +These assignment modifiers always expand to +nothing, so if appearing in a rule line by themselves should be +preceded with something to keep +.Nm +happy. +.Pp +The +.Ql Cm \&:: +helps avoid false matches with the +.At V +style +.Cm \&:= +modifier and since substitution always occurs the +.Cm \&::= +form is vaguely appropriate. +.It Cm \&::?= Ns Ar str +As for +.Cm \&::= +but only if the variable does not already have a value. +.It Cm \&::+= Ns Ar str +Append +.Ar str +to the variable. +.It Cm \&::!= Ns Ar cmd +Assign the output of +.Ar cmd +to the variable. +.It Cm \&:\&[ Ns Ar range Ns Cm \&] +Selects one or more words from the value, +or performs other operations related to the way in which the +value is divided into words. +.Pp +Ordinarily, a value is treated as a sequence of words +delimited by white space. +Some modifiers suppress this behavior, +causing a value to be treated as a single word +(possibly containing embedded white space). +An empty value, or a value that consists entirely of white-space, +is treated as a single word. +For the purposes of the +.Ql Cm \&:[] +modifier, the words are indexed both forwards using positive integers +(where index 1 represents the first word), +and backwards using negative integers +(where index \-1 represents the last word). +.Pp +The +.Ar range +is subjected to variable expansion, and the expanded result is +then interpreted as follows: +.Bl -tag -width index +.\" :[n] +.It Ar index +Selects a single word from the value. +.\" :[start..end] +.It Ar start Ns Cm \&.. Ns Ar end +Selects all words from +.Ar start +to +.Ar end , +inclusive. +For example, +.Ql Cm \&:[2..-1] +selects all words from the second word to the last word. +If +.Ar start +is greater than +.Ar end , +then the words are output in reverse order. +For example, +.Ql Cm \&:[-1..1] +selects all the words from last to first. +.\" :[*] +.It Cm \&* +Causes subsequent modifiers to treat the value as a single word +(possibly containing embedded white space). +Analogous to the effect of +\&"$*\&" +in Bourne shell. +.\" :[0] +.It 0 +Means the same as +.Ql Cm \&:[*] . +.\" :[*] +.It Cm \&@ +Causes subsequent modifiers to treat the value as a sequence of words +delimited by white space. +Analogous to the effect of +\&"$@\&" +in Bourne shell. +.\" :[#] +.It Cm \&# +Returns the number of words in the value. +.El \" :[range] +.El +.Sh INCLUDE STATEMENTS, CONDITIONALS AND FOR LOOPS +Makefile inclusion, conditional structures and for loops reminiscent +of the C programming language are provided in +.Nm . +All such structures are identified by a line beginning with a single +dot +.Pq Ql \&. +character. +Files are included with either +.Cm \&.include Aq Ar file +or +.Cm \&.include Pf \*q Ar file Ns \*q . +Variables between the angle brackets or double quotes are expanded +to form the file name. +If angle brackets are used, the included makefile is expected to be in +the system makefile directory. +If double quotes are used, the including makefile's directory and any +directories specified using the +.Fl I +option are searched before the system +makefile directory. +For compatibility with other versions of +.Nm +.Ql include file ... +is also accepted. +.Pp +If the include statement is written as +.Cm .-include +or as +.Cm .sinclude +then errors locating and/or opening include files are ignored. +.Pp +If the include statement is written as +.Cm .dinclude +not only are errors locating and/or opening include files ignored, +but stale dependencies within the included file will be ignored +just like +.Va .MAKE.DEPENDFILE . +.Pp +Conditional expressions are also preceded by a single dot as the first +character of a line. +The possible conditionals are as follows: +.Bl -tag -width Ds +.It Ic .error Ar message +The message is printed along with the name of the makefile and line number, +then +.Nm +will exit. +.It Ic .export Ar variable ... +Export the specified global variable. +If no variable list is provided, all globals are exported +except for internal variables (those that start with +.Ql \&. ) . +This is not affected by the +.Fl X +flag, so should be used with caution. +For compatibility with other +.Nm +programs +.Ql export variable=value +is also accepted. +.Pp +Appending a variable name to +.Va .MAKE.EXPORTED +is equivalent to exporting a variable. +.It Ic .export-env Ar variable ... +The same as +.Ql .export , +except that the variable is not appended to +.Va .MAKE.EXPORTED . +This allows exporting a value to the environment which is different from that +used by +.Nm +internally. +.It Ic .export-literal Ar variable ... +The same as +.Ql .export-env , +except that variables in the value are not expanded. +.It Ic .info Ar message +The message is printed along with the name of the makefile and line number. +.It Ic .undef Ar variable +Un-define the specified global variable. +Only global variables may be un-defined. +.It Ic .unexport Ar variable ... +The opposite of +.Ql .export . +The specified global +.Va variable +will be removed from +.Va .MAKE.EXPORTED . +If no variable list is provided, all globals are unexported, +and +.Va .MAKE.EXPORTED +deleted. +.It Ic .unexport-env +Unexport all globals previously exported and +clear the environment inherited from the parent. +This operation will cause a memory leak of the original environment, +so should be used sparingly. +Testing for +.Va .MAKE.LEVEL +being 0, would make sense. +Also note that any variables which originated in the parent environment +should be explicitly preserved if desired. +For example: +.Bd -literal -offset indent +.Li .if ${.MAKE.LEVEL} == 0 +PATH := ${PATH} +.Li .unexport-env +.Li .export PATH +.Li .endif +.Pp +.Ed +Would result in an environment containing only +.Ql Ev PATH , +which is the minimal useful environment. +Actually +.Ql Ev .MAKE.LEVEL +will also be pushed into the new environment. +.It Ic .warning Ar message +The message prefixed by +.Ql Pa warning: +is printed along with the name of the makefile and line number. +.It Ic \&.if Oo \&! Oc Ns Ar expression Op Ar operator expression ... +Test the value of an expression. +.It Ic .ifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ... +Test the value of a variable. +.It Ic .ifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ... +Test the value of a variable. +.It Ic .ifmake Oo \&! Oc Ns Ar target Op Ar operator target ... +Test the target being built. +.It Ic .ifnmake Oo \&! Ns Oc Ar target Op Ar operator target ... +Test the target being built. +.It Ic .else +Reverse the sense of the last conditional. +.It Ic .elif Oo \&! Ns Oc Ar expression Op Ar operator expression ... +A combination of +.Ql Ic .else +followed by +.Ql Ic .if . +.It Ic .elifdef Oo \&! Oc Ns Ar variable Op Ar operator variable ... +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifdef . +.It Ic .elifndef Oo \&! Oc Ns Ar variable Op Ar operator variable ... +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifndef . +.It Ic .elifmake Oo \&! Oc Ns Ar target Op Ar operator target ... +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifmake . +.It Ic .elifnmake Oo \&! Oc Ns Ar target Op Ar operator target ... +A combination of +.Ql Ic .else +followed by +.Ql Ic .ifnmake . +.It Ic .endif +End the body of the conditional. +.El +.Pp +The +.Ar operator +may be any one of the following: +.Bl -tag -width "Cm XX" +.It Cm \&|\&| +Logical OR. +.It Cm \&&& +Logical +.Tn AND ; +of higher precedence than +.Dq \&|\&| . +.El +.Pp +As in C, +.Nm +will only evaluate a conditional as far as is necessary to determine +its value. +Parentheses may be used to change the order of evaluation. +The boolean operator +.Ql Ic \&! +may be used to logically negate an entire +conditional. +It is of higher precedence than +.Ql Ic \&&& . +.Pp +The value of +.Ar expression +may be any of the following: +.Bl -tag -width defined +.It Ic defined +Takes a variable name as an argument and evaluates to true if the variable +has been defined. +.It Ic make +Takes a target name as an argument and evaluates to true if the target +was specified as part of +.Nm Ns 's +command line or was declared the default target (either implicitly or +explicitly, see +.Va .MAIN ) +before the line containing the conditional. +.It Ic empty +Takes a variable, with possible modifiers, and evaluates to true if +the expansion of the variable would result in an empty string. +.It Ic exists +Takes a file name as an argument and evaluates to true if the file exists. +The file is searched for on the system search path (see +.Va .PATH ) . +.It Ic target +Takes a target name as an argument and evaluates to true if the target +has been defined. +.It Ic commands +Takes a target name as an argument and evaluates to true if the target +has been defined and has commands associated with it. +.El +.Pp +.Ar Expression +may also be an arithmetic or string comparison. +Variable expansion is +performed on both sides of the comparison, after which the integral +values are compared. +A value is interpreted as hexadecimal if it is +preceded by 0x, otherwise it is decimal; octal numbers are not supported. +The standard C relational operators are all supported. +If after +variable expansion, either the left or right hand side of a +.Ql Ic == +or +.Ql Ic "!=" +operator is not an integral value, then +string comparison is performed between the expanded +variables. +If no relational operator is given, it is assumed that the expanded +variable is being compared against 0 or an empty string in the case +of a string comparison. +.Pp +When +.Nm +is evaluating one of these conditional expressions, and it encounters +a (white-space separated) word it doesn't recognize, either the +.Dq make +or +.Dq defined +expression is applied to it, depending on the form of the conditional. +If the form is +.Ql Ic .ifdef , +.Ql Ic .ifndef , +or +.Ql Ic .if +the +.Dq defined +expression is applied. +Similarly, if the form is +.Ql Ic .ifmake +or +.Ql Ic .ifnmake , +the +.Dq make +expression is applied. +.Pp +If the conditional evaluates to true the parsing of the makefile continues +as before. +If it evaluates to false, the following lines are skipped. +In both cases this continues until a +.Ql Ic .else +or +.Ql Ic .endif +is found. +.Pp +For loops are typically used to apply a set of rules to a list of files. +The syntax of a for loop is: +.Pp +.Bl -tag -compact -width Ds +.It Ic \&.for Ar variable Oo Ar variable ... Oc Ic in Ar expression +.It Aq make-rules +.It Ic \&.endfor +.El +.Pp +After the for +.Ic expression +is evaluated, it is split into words. +On each iteration of the loop, one word is taken and assigned to each +.Ic variable , +in order, and these +.Ic variables +are substituted into the +.Ic make-rules +inside the body of the for loop. +The number of words must come out even; that is, if there are three +iteration variables, the number of words provided must be a multiple +of three. +.Sh COMMENTS +Comments begin with a hash +.Pq Ql \&# +character, anywhere but in a shell +command line, and continue to the end of an unescaped new line. +.Sh SPECIAL SOURCES (ATTRIBUTES) +.Bl -tag -width .IGNOREx +.It Ic .EXEC +Target is never out of date, but always execute commands anyway. +.It Ic .IGNORE +Ignore any errors from the commands associated with this target, exactly +as if they all were preceded by a dash +.Pq Ql \- . +.\" .It Ic .INVISIBLE +.\" XXX +.\" .It Ic .JOIN +.\" XXX +.It Ic .MADE +Mark all sources of this target as being up-to-date. +.It Ic .MAKE +Execute the commands associated with this target even if the +.Fl n +or +.Fl t +options were specified. +Normally used to mark recursive +.Nm Ns s . +.It Ic .META +Create a meta file for the target, even if it is flagged as +.Ic .PHONY , +.Ic .MAKE , +or +.Ic .SPECIAL . +Usage in conjunction with +.Ic .MAKE +is the most likely case. +In "meta" mode, the target is out-of-date if the meta file is missing. +.It Ic .NOMETA +Do not create a meta file for the target. +Meta files are also not created for +.Ic .PHONY , +.Ic .MAKE , +or +.Ic .SPECIAL +targets. +.It Ic .NOMETA_CMP +Ignore differences in commands when deciding if target is out of date. +This is useful if the command contains a value which always changes. +If the number of commands change, though, the target will still be out of date. +The same effect applies to any command line that uses the variable +.Va .OODATE , +which can be used for that purpose even when not otherwise needed or desired: +.Bd -literal -offset indent + +skip-compare-for-some: + @echo this will be compared + @echo this will not ${.OODATE:M.NOMETA_CMP} + @echo this will also be compared + +.Ed +The +.Cm \&:M +pattern suppresses any expansion of the unwanted variable. +.It Ic .NOPATH +Do not search for the target in the directories specified by +.Ic .PATH . +.It Ic .NOTMAIN +Normally +.Nm +selects the first target it encounters as the default target to be built +if no target was specified. +This source prevents this target from being selected. +.It Ic .OPTIONAL +If a target is marked with this attribute and +.Nm +can't figure out how to create it, it will ignore this fact and assume +the file isn't needed or already exists. +.It Ic .PHONY +The target does not +correspond to an actual file; it is always considered to be out of date, +and will not be created with the +.Fl t +option. +Suffix-transformation rules are not applied to +.Ic .PHONY +targets. +.It Ic .PRECIOUS +When +.Nm +is interrupted, it normally removes any partially made targets. +This source prevents the target from being removed. +.It Ic .RECURSIVE +Synonym for +.Ic .MAKE . +.It Ic .SILENT +Do not echo any of the commands associated with this target, exactly +as if they all were preceded by an at sign +.Pq Ql @ . +.It Ic .USE +Turn the target into +.Nm Ns 's +version of a macro. +When the target is used as a source for another target, the other target +acquires the commands, sources, and attributes (except for +.Ic .USE ) +of the +source. +If the target already has commands, the +.Ic .USE +target's commands are appended +to them. +.It Ic .USEBEFORE +Exactly like +.Ic .USE , +but prepend the +.Ic .USEBEFORE +target commands to the target. +.It Ic .WAIT +If +.Ic .WAIT +appears in a dependency line, the sources that precede it are +made before the sources that succeed it in the line. +Since the dependents of files are not made until the file itself +could be made, this also stops the dependents being built unless they +are needed for another branch of the dependency tree. +So given: +.Bd -literal +x: a .WAIT b + echo x +a: + echo a +b: b1 + echo b +b1: + echo b1 + +.Ed +the output is always +.Ql a , +.Ql b1 , +.Ql b , +.Ql x . +.br +The ordering imposed by +.Ic .WAIT +is only relevant for parallel makes. +.El +.Sh SPECIAL TARGETS +Special targets may not be included with other targets, i.e. they must be +the only target specified. +.Bl -tag -width .BEGINx +.It Ic .BEGIN +Any command lines attached to this target are executed before anything +else is done. +.It Ic .DEFAULT +This is sort of a +.Ic .USE +rule for any target (that was used only as a +source) that +.Nm +can't figure out any other way to create. +Only the shell script is used. +The +.Ic .IMPSRC +variable of a target that inherits +.Ic .DEFAULT Ns 's +commands is set +to the target's own name. +.It Ic .DELETE_ON_ERROR +If this target is present in the makefile, it globally causes make to +delete targets whose commands fail. +(By default, only targets whose commands are interrupted during +execution are deleted. +This is the historical behavior.) +This setting can be used to help prevent half-finished or malformed +targets from being left around and corrupting future rebuilds. +.It Ic .END +Any command lines attached to this target are executed after everything +else is done. +.It Ic .ERROR +Any command lines attached to this target are executed when another target fails. +The +.Ic .ERROR_TARGET +variable is set to the target that failed. +See also +.Ic MAKE_PRINT_VAR_ON_ERROR . +.It Ic .IGNORE +Mark each of the sources with the +.Ic .IGNORE +attribute. +If no sources are specified, this is the equivalent of specifying the +.Fl i +option. +.It Ic .INTERRUPT +If +.Nm +is interrupted, the commands for this target will be executed. +.It Ic .MAIN +If no target is specified when +.Nm +is invoked, this target will be built. +.It Ic .MAKEFLAGS +This target provides a way to specify flags for +.Nm +when the makefile is used. +The flags are as if typed to the shell, though the +.Fl f +option will have +no effect. +.\" XXX: NOT YET!!!! +.\" .It Ic .NOTPARALLEL +.\" The named targets are executed in non parallel mode. +.\" If no targets are +.\" specified, then all targets are executed in non parallel mode. +.It Ic .NOPATH +Apply the +.Ic .NOPATH +attribute to any specified sources. +.It Ic .NOTPARALLEL +Disable parallel mode. +.It Ic .NO_PARALLEL +Synonym for +.Ic .NOTPARALLEL , +for compatibility with other pmake variants. +.It Ic .OBJDIR +The source is a new value for +.Ql Va .OBJDIR . +If it exists, +.Nm +will +.Xr chdir 2 +to it and update the value of +.Ql Va .OBJDIR . +.It Ic .ORDER +The named targets are made in sequence. +This ordering does not add targets to the list of targets to be made. +Since the dependents of a target do not get built until the target itself +could be built, unless +.Ql a +is built by another part of the dependency graph, +the following is a dependency loop: +.Bd -literal +\&.ORDER: b a +b: a +.Ed +.Pp +The ordering imposed by +.Ic .ORDER +is only relevant for parallel makes. +.\" XXX: NOT YET!!!! +.\" .It Ic .PARALLEL +.\" The named targets are executed in parallel mode. +.\" If no targets are +.\" specified, then all targets are executed in parallel mode. +.It Ic .PATH +The sources are directories which are to be searched for files not +found in the current directory. +If no sources are specified, any previously specified directories are +deleted. +If the source is the special +.Ic .DOTLAST +target, then the current working +directory is searched last. +.It Ic .PATH. Ns Va suffix +Like +.Ic .PATH +but applies only to files with a particular suffix. +The suffix must have been previously declared with +.Ic .SUFFIXES . +.It Ic .PHONY +Apply the +.Ic .PHONY +attribute to any specified sources. +.It Ic .PRECIOUS +Apply the +.Ic .PRECIOUS +attribute to any specified sources. +If no sources are specified, the +.Ic .PRECIOUS +attribute is applied to every +target in the file. +.It Ic .SHELL +Sets the shell that +.Nm +will use to execute commands. +The sources are a set of +.Ar field=value +pairs. +.Bl -tag -width hasErrCtls +.It Ar name +This is the minimal specification, used to select one of the built-in +shell specs; +.Ar sh , +.Ar ksh , +and +.Ar csh . +.It Ar path +Specifies the path to the shell. +.It Ar hasErrCtl +Indicates whether the shell supports exit on error. +.It Ar check +The command to turn on error checking. +.It Ar ignore +The command to disable error checking. +.It Ar echo +The command to turn on echoing of commands executed. +.It Ar quiet +The command to turn off echoing of commands executed. +.It Ar filter +The output to filter after issuing the +.Ar quiet +command. +It is typically identical to +.Ar quiet . +.It Ar errFlag +The flag to pass the shell to enable error checking. +.It Ar echoFlag +The flag to pass the shell to enable command echoing. +.It Ar newline +The string literal to pass the shell that results in a single newline +character when used outside of any quoting characters. +.El +Example: +.Bd -literal +\&.SHELL: name=ksh path=/bin/ksh hasErrCtl=true \e + check="set \-e" ignore="set +e" \e + echo="set \-v" quiet="set +v" filter="set +v" \e + echoFlag=v errFlag=e newline="'\en'" +.Ed +.It Ic .SILENT +Apply the +.Ic .SILENT +attribute to any specified sources. +If no sources are specified, the +.Ic .SILENT +attribute is applied to every +command in the file. +.It Ic .STALE +This target gets run when a dependency file contains stale entries, having +.Va .ALLSRC +set to the name of that dependency file. +.It Ic .SUFFIXES +Each source specifies a suffix to +.Nm . +If no sources are specified, any previously specified suffixes are deleted. +It allows the creation of suffix-transformation rules. +.Pp +Example: +.Bd -literal +\&.SUFFIXES: .o +\&.c.o: + cc \-o ${.TARGET} \-c ${.IMPSRC} +.Ed +.El +.Sh ENVIRONMENT +.Nm +uses the following environment variables, if they exist: +.Ev MACHINE , +.Ev MACHINE_ARCH , +.Ev MAKE , +.Ev MAKEFLAGS , +.Ev MAKEOBJDIR , +.Ev MAKEOBJDIRPREFIX , +.Ev MAKESYSPATH , +.Ev PWD , +and +.Ev TMPDIR . +.Pp +.Ev MAKEOBJDIRPREFIX +and +.Ev MAKEOBJDIR +may only be set in the environment or on the command line to +.Nm +and not as makefile variables; +see the description of +.Ql Va .OBJDIR +for more details. +.Sh FILES +.Bl -tag -width /usr/share/mk -compact +.It .depend +list of dependencies +.It Makefile +list of dependencies +.It makefile +list of dependencies +.It sys.mk +system makefile +.It /usr/share/mk +system makefile directory +.El +.Sh COMPATIBILITY +The basic make syntax is compatible between different versions of make; +however the special variables, variable modifiers and conditionals are not. +.Ss Older versions +An incomplete list of changes in older versions of +.Nm : +.Pp +The way that .for loop variables are substituted changed after +.Nx 5.0 +so that they still appear to be variable expansions. +In particular this stops them being treated as syntax, and removes some +obscure problems using them in .if statements. +.Pp +The way that parallel makes are scheduled changed in +.Nx 4.0 +so that .ORDER and .WAIT apply recursively to the dependent nodes. +The algorithms used may change again in the future. +.Ss Other make dialects +Other make dialects (GNU make, SVR4 make, POSIX make, etc.) do not +support most of the features of +.Nm +as described in this manual. +Most notably: +.Bl -bullet -offset indent +.It +The +.Ic .WAIT +and +.Ic .ORDER +declarations and most functionality pertaining to parallelization. +(GNU make supports parallelization but lacks these features needed to +control it effectively.) +.It +Directives, including for loops and conditionals and most of the +forms of include files. +(GNU make has its own incompatible and less powerful syntax for +conditionals.) +.It +All built-in variables that begin with a dot. +.It +Most of the special sources and targets that begin with a dot, +with the notable exception of +.Ic .PHONY , +.Ic .PRECIOUS , +and +.Ic .SUFFIXES . +.It +Variable modifiers, except for the +.Dl :old=new +string substitution, which does not portably support globbing with +.Ql % +and historically only works on declared suffixes. +.It +The +.Ic $> +variable even in its short form; most makes support this functionality +but its name varies. +.El +.Pp +Some features are somewhat more portable, such as assignment with +.Ic += , +.Ic ?= , +and +.Ic != . +The +.Ic .PATH +functionality is based on an older feature +.Ic VPATH +found in GNU make and many versions of SVR4 make; however, +historically its behavior is too ill-defined (and too buggy) to rely +upon. +.Pp +The +.Ic $@ +and +.Ic $< +variables are more or less universally portable, as is the +.Ic $(MAKE) +variable. +Basic use of suffix rules (for files only in the current directory, +not trying to chain transformations together, etc.) is also reasonably +portable. +.Sh SEE ALSO +.Xr mkdep 1 +.Sh HISTORY +A +.Nm +command appeared in +.At v7 . +This +.Nm +implementation is based on Adam De Boor's pmake program which was written +for Sprite at Berkeley. +It was designed to be a parallel distributed make running jobs on different +machines using a daemon called +.Dq customs . +.Pp +Historically the target/dependency +.Dq FRC +has been used to FoRCe rebuilding (since the target/dependency +does not exist... unless someone creates an +.Dq FRC +file). +.Sh BUGS +The +.Nm +syntax is difficult to parse without actually acting of the data. +For instance finding the end of a variable use should involve scanning each +the modifiers using the correct terminator for each field. +In many places +.Nm +just counts {} and () in order to find the end of a variable expansion. +.Pp +There is no way of escaping a space character in a filename. diff --git a/usr.bin/make/make.c b/usr.bin/make/make.c new file mode 100644 index 0000000..8947582 --- /dev/null +++ b/usr.bin/make/make.c @@ -0,0 +1,1555 @@ +/* $NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)make.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: make.c,v 1.96 2016/11/10 23:41:58 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * make.c -- + * The functions which perform the examination of targets and + * their suitability for creation + * + * Interface: + * Make_Run Initialize things for the module and recreate + * whatever needs recreating. Returns TRUE if + * work was (or would have been) done and FALSE + * otherwise. + * + * Make_Update Update all parents of a given child. Performs + * various bookkeeping chores like the updating + * of the cmgn field of the parent, filling + * of the IMPSRC context variable, etc. It will + * place the parent on the toBeMade queue if it + * should be. + * + * Make_TimeStamp Function to set the parent's cmgn field + * based on a child's modification time. + * + * Make_DoAllVar Set up the various local variables for a + * target, including the .ALLSRC variable, making + * sure that any variable that needs to exist + * at the very least has the empty value. + * + * Make_OODate Determine if a target is out-of-date. + * + * Make_HandleUse See if a child is a .USE node for a parent + * and perform the .USE actions if so. + * + * Make_ExpandUse Expand .USE nodes + */ + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" + +static unsigned int checked = 1;/* Sequence # to detect recursion */ +static Lst toBeMade; /* The current fringe of the graph. These + * are nodes which await examination by + * MakeOODate. It is added to by + * Make_Update and subtracted from by + * MakeStartJobs */ + +static int MakeAddChild(void *, void *); +static int MakeFindChild(void *, void *); +static int MakeUnmark(void *, void *); +static int MakeAddAllSrc(void *, void *); +static int MakeTimeStamp(void *, void *); +static int MakeHandleUse(void *, void *); +static Boolean MakeStartJobs(void); +static int MakePrintStatus(void *, void *); +static int MakeCheckOrder(void *, void *); +static int MakeBuildChild(void *, void *); +static int MakeBuildParent(void *, void *); + +MAKE_ATTR_DEAD static void +make_abort(GNode *gn, int line) +{ + static int two = 2; + + fprintf(debug_file, "make_abort from line %d\n", line); + Targ_PrintNode(gn, &two); + Lst_ForEach(toBeMade, Targ_PrintNode, &two); + Targ_PrintGraph(3); + abort(); +} + +/*- + *----------------------------------------------------------------------- + * Make_TimeStamp -- + * Set the cmgn field of a parent node based on the mtime stamp in its + * child. Called from MakeOODate via Lst_ForEach. + * + * Input: + * pgn the current parent + * cgn the child we've just examined + * + * Results: + * Always returns 0. + * + * Side Effects: + * The cmgn of the parent node will be changed if the mtime + * field of the child is greater than it. + *----------------------------------------------------------------------- + */ +int +Make_TimeStamp(GNode *pgn, GNode *cgn) +{ + if (pgn->cmgn == NULL || cgn->mtime > pgn->cmgn->mtime) { + pgn->cmgn = cgn; + } + return (0); +} + +/* + * Input: + * pgn the current parent + * cgn the child we've just examined + * + */ +static int +MakeTimeStamp(void *pgn, void *cgn) +{ + return Make_TimeStamp((GNode *)pgn, (GNode *)cgn); +} + +/*- + *----------------------------------------------------------------------- + * Make_OODate -- + * See if a given node is out of date with respect to its sources. + * Used by Make_Run when deciding which nodes to place on the + * toBeMade queue initially and by Make_Update to screen out USE and + * EXEC nodes. In the latter case, however, any other sort of node + * must be considered out-of-date since at least one of its children + * will have been recreated. + * + * Input: + * gn the node to check + * + * Results: + * TRUE if the node is out of date. FALSE otherwise. + * + * Side Effects: + * The mtime field of the node and the cmgn field of its parents + * will/may be changed. + *----------------------------------------------------------------------- + */ +Boolean +Make_OODate(GNode *gn) +{ + Boolean oodate; + + /* + * Certain types of targets needn't even be sought as their datedness + * doesn't depend on their modification time... + */ + if ((gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC)) == 0) { + (void)Dir_MTime(gn, 1); + if (DEBUG(MAKE)) { + if (gn->mtime != 0) { + fprintf(debug_file, "modified %s...", Targ_FmtTime(gn->mtime)); + } else { + fprintf(debug_file, "non-existent..."); + } + } + } + + /* + * A target is remade in one of the following circumstances: + * its modification time is smaller than that of its youngest child + * and it would actually be run (has commands or type OP_NOP) + * it's the object of a force operator + * it has no children, was on the lhs of an operator and doesn't exist + * already. + * + * Libraries are only considered out-of-date if the archive module says + * they are. + * + * These weird rules are brought to you by Backward-Compatibility and + * the strange people who wrote 'Make'. + */ + if (gn->type & (OP_USE|OP_USEBEFORE)) { + /* + * If the node is a USE node it is *never* out of date + * no matter *what*. + */ + if (DEBUG(MAKE)) { + fprintf(debug_file, ".USE node..."); + } + oodate = FALSE; + } else if ((gn->type & OP_LIB) && + ((gn->mtime==0) || Arch_IsLib(gn))) { + if (DEBUG(MAKE)) { + fprintf(debug_file, "library..."); + } + + /* + * always out of date if no children and :: target + * or non-existent. + */ + oodate = (gn->mtime == 0 || Arch_LibOODate(gn) || + (gn->cmgn == NULL && (gn->type & OP_DOUBLEDEP))); + } else if (gn->type & OP_JOIN) { + /* + * A target with the .JOIN attribute is only considered + * out-of-date if any of its children was out-of-date. + */ + if (DEBUG(MAKE)) { + fprintf(debug_file, ".JOIN node..."); + } + if (DEBUG(MAKE)) { + fprintf(debug_file, "source %smade...", gn->flags & CHILDMADE ? "" : "not "); + } + oodate = (gn->flags & CHILDMADE) ? TRUE : FALSE; + } else if (gn->type & (OP_FORCE|OP_EXEC|OP_PHONY)) { + /* + * A node which is the object of the force (!) operator or which has + * the .EXEC attribute is always considered out-of-date. + */ + if (DEBUG(MAKE)) { + if (gn->type & OP_FORCE) { + fprintf(debug_file, "! operator..."); + } else if (gn->type & OP_PHONY) { + fprintf(debug_file, ".PHONY node..."); + } else { + fprintf(debug_file, ".EXEC node..."); + } + } + oodate = TRUE; + } else if ((gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) || + (gn->cmgn == NULL && + ((gn->mtime == 0 && !(gn->type & OP_OPTIONAL)) + || gn->type & OP_DOUBLEDEP))) + { + /* + * A node whose modification time is less than that of its + * youngest child or that has no children (cmgn == NULL) and + * either doesn't exist (mtime == 0) and it isn't optional + * or was the object of a * :: operator is out-of-date. + * Why? Because that's the way Make does it. + */ + if (DEBUG(MAKE)) { + if (gn->cmgn != NULL && gn->mtime < gn->cmgn->mtime) { + fprintf(debug_file, "modified before source %s...", + gn->cmgn->path ? gn->cmgn->path : gn->cmgn->name); + } else if (gn->mtime == 0) { + fprintf(debug_file, "non-existent and no sources..."); + } else { + fprintf(debug_file, ":: operator and no sources..."); + } + } + oodate = TRUE; + } else { + /* + * When a non-existing child with no sources + * (such as a typically used FORCE source) has been made and + * the target of the child (usually a directory) has the same + * timestamp as the timestamp just given to the non-existing child + * after it was considered made. + */ + if (DEBUG(MAKE)) { + if (gn->flags & FORCE) + fprintf(debug_file, "non existing child..."); + } + oodate = (gn->flags & FORCE) ? TRUE : FALSE; + } + +#ifdef USE_META + if (useMeta) { + oodate = meta_oodate(gn, oodate); + } +#endif + + /* + * If the target isn't out-of-date, the parents need to know its + * modification time. Note that targets that appear to be out-of-date + * but aren't, because they have no commands and aren't of type OP_NOP, + * have their mtime stay below their children's mtime to keep parents from + * thinking they're out-of-date. + */ + if (!oodate) { + Lst_ForEach(gn->parents, MakeTimeStamp, gn); + } + + return (oodate); +} + +/*- + *----------------------------------------------------------------------- + * MakeAddChild -- + * Function used by Make_Run to add a child to the list l. + * It will only add the child if its make field is FALSE. + * + * Input: + * gnp the node to add + * lp the list to which to add it + * + * Results: + * Always returns 0 + * + * Side Effects: + * The given list is extended + *----------------------------------------------------------------------- + */ +static int +MakeAddChild(void *gnp, void *lp) +{ + GNode *gn = (GNode *)gnp; + Lst l = (Lst) lp; + + if ((gn->flags & REMAKE) == 0 && !(gn->type & (OP_USE|OP_USEBEFORE))) { + if (DEBUG(MAKE)) + fprintf(debug_file, "MakeAddChild: need to examine %s%s\n", + gn->name, gn->cohort_num); + (void)Lst_EnQueue(l, gn); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * MakeFindChild -- + * Function used by Make_Run to find the pathname of a child + * that was already made. + * + * Input: + * gnp the node to find + * + * Results: + * Always returns 0 + * + * Side Effects: + * The path and mtime of the node and the cmgn of the parent are + * updated; the unmade children count of the parent is decremented. + *----------------------------------------------------------------------- + */ +static int +MakeFindChild(void *gnp, void *pgnp) +{ + GNode *gn = (GNode *)gnp; + GNode *pgn = (GNode *)pgnp; + + (void)Dir_MTime(gn, 0); + Make_TimeStamp(pgn, gn); + pgn->unmade--; + + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_HandleUse -- + * Function called by Make_Run and SuffApplyTransform on the downward + * pass to handle .USE and transformation nodes. It implements the + * .USE and transformation functionality by copying the node's commands, + * type flags and children to the parent node. + * + * A .USE node is much like an explicit transformation rule, except + * its commands are always added to the target node, even if the + * target already has commands. + * + * Input: + * cgn The .USE node + * pgn The target of the .USE node + * + * Results: + * none + * + * Side Effects: + * Children and commands may be added to the parent and the parent's + * type may be changed. + * + *----------------------------------------------------------------------- + */ +void +Make_HandleUse(GNode *cgn, GNode *pgn) +{ + LstNode ln; /* An element in the children list */ + +#ifdef DEBUG_SRC + if ((cgn->type & (OP_USE|OP_USEBEFORE|OP_TRANSFORM)) == 0) { + fprintf(debug_file, "Make_HandleUse: called for plain node %s\n", cgn->name); + return; + } +#endif + + if ((cgn->type & (OP_USE|OP_USEBEFORE)) || Lst_IsEmpty(pgn->commands)) { + if (cgn->type & OP_USEBEFORE) { + /* + * .USEBEFORE -- + * prepend the child's commands to the parent. + */ + Lst cmds = pgn->commands; + pgn->commands = Lst_Duplicate(cgn->commands, NULL); + (void)Lst_Concat(pgn->commands, cmds, LST_CONCNEW); + Lst_Destroy(cmds, NULL); + } else { + /* + * .USE or target has no commands -- + * append the child's commands to the parent. + */ + (void)Lst_Concat(pgn->commands, cgn->commands, LST_CONCNEW); + } + } + + if (Lst_Open(cgn->children) == SUCCESS) { + while ((ln = Lst_Next(cgn->children)) != NULL) { + GNode *tgn, *gn = (GNode *)Lst_Datum(ln); + + /* + * Expand variables in the .USE node's name + * and save the unexpanded form. + * We don't need to do this for commands. + * They get expanded properly when we execute. + */ + if (gn->uname == NULL) { + gn->uname = gn->name; + } else { + free(gn->name); + } + gn->name = Var_Subst(NULL, gn->uname, pgn, VARF_WANTRES); + if (gn->name && gn->uname && strcmp(gn->name, gn->uname) != 0) { + /* See if we have a target for this node. */ + tgn = Targ_FindNode(gn->name, TARG_NOCREATE); + if (tgn != NULL) + gn = tgn; + } + + (void)Lst_AtEnd(pgn->children, gn); + (void)Lst_AtEnd(gn->parents, pgn); + pgn->unmade += 1; + } + Lst_Close(cgn->children); + } + + pgn->type |= cgn->type & ~(OP_OPMASK|OP_USE|OP_USEBEFORE|OP_TRANSFORM); +} + +/*- + *----------------------------------------------------------------------- + * MakeHandleUse -- + * Callback function for Lst_ForEach, used by Make_Run on the downward + * pass to handle .USE nodes. Should be called before the children + * are enqueued to be looked at by MakeAddChild. + * This function calls Make_HandleUse to copy the .USE node's commands, + * type flags and children to the parent node. + * + * Input: + * cgnp the child we've just examined + * pgnp the current parent + * + * Results: + * returns 0. + * + * Side Effects: + * After expansion, .USE child nodes are removed from the parent + * + *----------------------------------------------------------------------- + */ +static int +MakeHandleUse(void *cgnp, void *pgnp) +{ + GNode *cgn = (GNode *)cgnp; + GNode *pgn = (GNode *)pgnp; + LstNode ln; /* An element in the children list */ + int unmarked; + + unmarked = ((cgn->type & OP_MARK) == 0); + cgn->type |= OP_MARK; + + if ((cgn->type & (OP_USE|OP_USEBEFORE)) == 0) + return (0); + + if (unmarked) + Make_HandleUse(cgn, pgn); + + /* + * This child node is now "made", so we decrement the count of + * unmade children in the parent... We also remove the child + * from the parent's list to accurately reflect the number of decent + * children the parent has. This is used by Make_Run to decide + * whether to queue the parent or examine its children... + */ + if ((ln = Lst_Member(pgn->children, cgn)) != NULL) { + Lst_Remove(pgn->children, ln); + pgn->unmade--; + } + return (0); +} + + +/*- + *----------------------------------------------------------------------- + * Make_Recheck -- + * Check the modification time of a gnode, and update it as described + * in the comments below. + * + * Results: + * returns 0 if the gnode does not exist, or its filesystem + * time if it does. + * + * Side Effects: + * the gnode's modification time and path name are affected. + * + *----------------------------------------------------------------------- + */ +time_t +Make_Recheck(GNode *gn) +{ + time_t mtime = Dir_MTime(gn, 1); + +#ifndef RECHECK + /* + * We can't re-stat the thing, but we can at least take care of rules + * where a target depends on a source that actually creates the + * target, but only if it has changed, e.g. + * + * parse.h : parse.o + * + * parse.o : parse.y + * yacc -d parse.y + * cc -c y.tab.c + * mv y.tab.o parse.o + * cmp -s y.tab.h parse.h || mv y.tab.h parse.h + * + * In this case, if the definitions produced by yacc haven't changed + * from before, parse.h won't have been updated and gn->mtime will + * reflect the current modification time for parse.h. This is + * something of a kludge, I admit, but it's a useful one.. + * XXX: People like to use a rule like + * + * FRC: + * + * To force things that depend on FRC to be made, so we have to + * check for gn->children being empty as well... + */ + if (!Lst_IsEmpty(gn->commands) || Lst_IsEmpty(gn->children)) { + gn->mtime = now; + } +#else + /* + * This is what Make does and it's actually a good thing, as it + * allows rules like + * + * cmp -s y.tab.h parse.h || cp y.tab.h parse.h + * + * to function as intended. Unfortunately, thanks to the stateless + * nature of NFS (by which I mean the loose coupling of two clients + * using the same file from a common server), there are times + * when the modification time of a file created on a remote + * machine will not be modified before the local stat() implied by + * the Dir_MTime occurs, thus leading us to believe that the file + * is unchanged, wreaking havoc with files that depend on this one. + * + * I have decided it is better to make too much than to make too + * little, so this stuff is commented out unless you're sure it's ok. + * -- ardeb 1/12/88 + */ + /* + * Christos, 4/9/92: If we are saving commands pretend that + * the target is made now. Otherwise archives with ... rules + * don't work! + */ + if (NoExecute(gn) || (gn->type & OP_SAVE_CMDS) || + (mtime == 0 && !(gn->type & OP_WAIT))) { + if (DEBUG(MAKE)) { + fprintf(debug_file, " recheck(%s): update time from %s to now\n", + gn->name, Targ_FmtTime(gn->mtime)); + } + gn->mtime = now; + } + else { + if (DEBUG(MAKE)) { + fprintf(debug_file, " recheck(%s): current update time: %s\n", + gn->name, Targ_FmtTime(gn->mtime)); + } + } +#endif + return mtime; +} + +/*- + *----------------------------------------------------------------------- + * Make_Update -- + * Perform update on the parents of a node. Used by JobFinish once + * a node has been dealt with and by MakeStartJobs if it finds an + * up-to-date node. + * + * Input: + * cgn the child node + * + * Results: + * Always returns 0 + * + * Side Effects: + * The unmade field of pgn is decremented and pgn may be placed on + * the toBeMade queue if this field becomes 0. + * + * If the child was made, the parent's flag CHILDMADE field will be + * set true. + * + * If the child is not up-to-date and still does not exist, + * set the FORCE flag on the parents. + * + * If the child wasn't made, the cmgn field of the parent will be + * altered if the child's mtime is big enough. + * + * Finally, if the child is the implied source for the parent, the + * parent's IMPSRC variable is set appropriately. + * + *----------------------------------------------------------------------- + */ +void +Make_Update(GNode *cgn) +{ + GNode *pgn; /* the parent node */ + char *cname; /* the child's name */ + LstNode ln; /* Element in parents and iParents lists */ + time_t mtime = -1; + char *p1; + Lst parents; + GNode *centurion; + + /* It is save to re-examine any nodes again */ + checked++; + + cname = Var_Value(TARGET, cgn, &p1); + free(p1); + + if (DEBUG(MAKE)) + fprintf(debug_file, "Make_Update: %s%s\n", cgn->name, cgn->cohort_num); + + /* + * If the child was actually made, see what its modification time is + * now -- some rules won't actually update the file. If the file still + * doesn't exist, make its mtime now. + */ + if (cgn->made != UPTODATE) { + mtime = Make_Recheck(cgn); + } + + /* + * If this is a `::' node, we must consult its first instance + * which is where all parents are linked. + */ + if ((centurion = cgn->centurion) != NULL) { + if (!Lst_IsEmpty(cgn->parents)) + Punt("%s%s: cohort has parents", cgn->name, cgn->cohort_num); + centurion->unmade_cohorts -= 1; + if (centurion->unmade_cohorts < 0) + Error("Graph cycles through centurion %s", centurion->name); + } else { + centurion = cgn; + } + parents = centurion->parents; + + /* If this was a .ORDER node, schedule the RHS */ + Lst_ForEach(centurion->order_succ, MakeBuildParent, Lst_First(toBeMade)); + + /* Now mark all the parents as having one less unmade child */ + if (Lst_Open(parents) == SUCCESS) { + while ((ln = Lst_Next(parents)) != NULL) { + pgn = (GNode *)Lst_Datum(ln); + if (DEBUG(MAKE)) + fprintf(debug_file, "inspect parent %s%s: flags %x, " + "type %x, made %d, unmade %d ", + pgn->name, pgn->cohort_num, pgn->flags, + pgn->type, pgn->made, pgn->unmade-1); + + if (!(pgn->flags & REMAKE)) { + /* This parent isn't needed */ + if (DEBUG(MAKE)) + fprintf(debug_file, "- not needed\n"); + continue; + } + if (mtime == 0 && !(cgn->type & OP_WAIT)) + pgn->flags |= FORCE; + + /* + * If the parent has the .MADE attribute, its timestamp got + * updated to that of its newest child, and its unmake + * child count got set to zero in Make_ExpandUse(). + * However other things might cause us to build one of its + * children - and so we mustn't do any processing here when + * the child build finishes. + */ + if (pgn->type & OP_MADE) { + if (DEBUG(MAKE)) + fprintf(debug_file, "- .MADE\n"); + continue; + } + + if ( ! (cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE))) { + if (cgn->made == MADE) + pgn->flags |= CHILDMADE; + (void)Make_TimeStamp(pgn, cgn); + } + + /* + * A parent must wait for the completion of all instances + * of a `::' dependency. + */ + if (centurion->unmade_cohorts != 0 || centurion->made < MADE) { + if (DEBUG(MAKE)) + fprintf(debug_file, + "- centurion made %d, %d unmade cohorts\n", + centurion->made, centurion->unmade_cohorts); + continue; + } + + /* One more child of this parent is now made */ + pgn->unmade -= 1; + if (pgn->unmade < 0) { + if (DEBUG(MAKE)) { + fprintf(debug_file, "Graph cycles through %s%s\n", + pgn->name, pgn->cohort_num); + Targ_PrintGraph(2); + } + Error("Graph cycles through %s%s", pgn->name, pgn->cohort_num); + } + + /* We must always rescan the parents of .WAIT and .ORDER nodes. */ + if (pgn->unmade != 0 && !(centurion->type & OP_WAIT) + && !(centurion->flags & DONE_ORDER)) { + if (DEBUG(MAKE)) + fprintf(debug_file, "- unmade children\n"); + continue; + } + if (pgn->made != DEFERRED) { + /* + * Either this parent is on a different branch of the tree, + * or it on the RHS of a .WAIT directive + * or it is already on the toBeMade list. + */ + if (DEBUG(MAKE)) + fprintf(debug_file, "- not deferred\n"); + continue; + } + if (pgn->order_pred + && Lst_ForEach(pgn->order_pred, MakeCheckOrder, 0)) { + /* A .ORDER rule stops us building this */ + continue; + } + if (DEBUG(MAKE)) { + static int two = 2; + fprintf(debug_file, "- %s%s made, schedule %s%s (made %d)\n", + cgn->name, cgn->cohort_num, + pgn->name, pgn->cohort_num, pgn->made); + Targ_PrintNode(pgn, &two); + } + /* Ok, we can schedule the parent again */ + pgn->made = REQUESTED; + (void)Lst_EnQueue(toBeMade, pgn); + } + Lst_Close(parents); + } + + /* + * Set the .PREFIX and .IMPSRC variables for all the implied parents + * of this node. + */ + if (Lst_Open(cgn->iParents) == SUCCESS) { + char *cpref = Var_Value(PREFIX, cgn, &p1); + + while ((ln = Lst_Next(cgn->iParents)) != NULL) { + pgn = (GNode *)Lst_Datum(ln); + if (pgn->flags & REMAKE) { + Var_Set(IMPSRC, cname, pgn, 0); + if (cpref != NULL) + Var_Set(PREFIX, cpref, pgn, 0); + } + } + free(p1); + Lst_Close(cgn->iParents); + } +} + +/*- + *----------------------------------------------------------------------- + * MakeAddAllSrc -- + * Add a child's name to the ALLSRC and OODATE variables of the given + * node. Called from Make_DoAllVar via Lst_ForEach. A child is added only + * if it has not been given the .EXEC, .USE or .INVISIBLE attributes. + * .EXEC and .USE children are very rarely going to be files, so... + * If the child is a .JOIN node, its ALLSRC is propagated to the parent. + * + * A child is added to the OODATE variable if its modification time is + * later than that of its parent, as defined by Make, except if the + * parent is a .JOIN node. In that case, it is only added to the OODATE + * variable if it was actually made (since .JOIN nodes don't have + * modification times, the comparison is rather unfair...).. + * + * Results: + * Always returns 0 + * + * Side Effects: + * The ALLSRC variable for the given node is extended. + *----------------------------------------------------------------------- + */ +static int +MakeUnmark(void *cgnp, void *pgnp MAKE_ATTR_UNUSED) +{ + GNode *cgn = (GNode *)cgnp; + + cgn->type &= ~OP_MARK; + return (0); +} + +/* + * Input: + * cgnp The child to add + * pgnp The parent to whose ALLSRC variable it should + * be added + * + */ +static int +MakeAddAllSrc(void *cgnp, void *pgnp) +{ + GNode *cgn = (GNode *)cgnp; + GNode *pgn = (GNode *)pgnp; + + if (cgn->type & OP_MARK) + return (0); + cgn->type |= OP_MARK; + + if ((cgn->type & (OP_EXEC|OP_USE|OP_USEBEFORE|OP_INVISIBLE)) == 0) { + char *child, *allsrc; + char *p1 = NULL, *p2 = NULL; + + if (cgn->type & OP_ARCHV) + child = Var_Value(MEMBER, cgn, &p1); + else + child = cgn->path ? cgn->path : cgn->name; + if (cgn->type & OP_JOIN) { + allsrc = Var_Value(ALLSRC, cgn, &p2); + } else { + allsrc = child; + } + if (allsrc != NULL) + Var_Append(ALLSRC, allsrc, pgn); + free(p2); + if (pgn->type & OP_JOIN) { + if (cgn->made == MADE) { + Var_Append(OODATE, child, pgn); + } + } else if ((pgn->mtime < cgn->mtime) || + (cgn->mtime >= now && cgn->made == MADE)) + { + /* + * It goes in the OODATE variable if the parent is younger than the + * child or if the child has been modified more recently than + * the start of the make. This is to keep pmake from getting + * confused if something else updates the parent after the + * make starts (shouldn't happen, I know, but sometimes it + * does). In such a case, if we've updated the kid, the parent + * is likely to have a modification time later than that of + * the kid and anything that relies on the OODATE variable will + * be hosed. + * + * XXX: This will cause all made children to go in the OODATE + * variable, even if they're not touched, if RECHECK isn't defined, + * since cgn->mtime is set to now in Make_Update. According to + * some people, this is good... + */ + Var_Append(OODATE, child, pgn); + } + free(p1); + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Make_DoAllVar -- + * Set up the ALLSRC and OODATE variables. Sad to say, it must be + * done separately, rather than while traversing the graph. This is + * because Make defined OODATE to contain all sources whose modification + * times were later than that of the target, *not* those sources that + * were out-of-date. Since in both compatibility and native modes, + * the modification time of the parent isn't found until the child + * has been dealt with, we have to wait until now to fill in the + * variable. As for ALLSRC, the ordering is important and not + * guaranteed when in native mode, so it must be set here, too. + * + * Results: + * None + * + * Side Effects: + * The ALLSRC and OODATE variables of the given node is filled in. + * If the node is a .JOIN node, its TARGET variable will be set to + * match its ALLSRC variable. + *----------------------------------------------------------------------- + */ +void +Make_DoAllVar(GNode *gn) +{ + if (gn->flags & DONE_ALLSRC) + return; + + Lst_ForEach(gn->children, MakeUnmark, gn); + Lst_ForEach(gn->children, MakeAddAllSrc, gn); + + if (!Var_Exists (OODATE, gn)) { + Var_Set(OODATE, "", gn, 0); + } + if (!Var_Exists (ALLSRC, gn)) { + Var_Set(ALLSRC, "", gn, 0); + } + + if (gn->type & OP_JOIN) { + char *p1; + Var_Set(TARGET, Var_Value(ALLSRC, gn, &p1), gn, 0); + free(p1); + } + gn->flags |= DONE_ALLSRC; +} + +/*- + *----------------------------------------------------------------------- + * MakeStartJobs -- + * Start as many jobs as possible. + * + * Results: + * If the query flag was given to pmake, no job will be started, + * but as soon as an out-of-date target is found, this function + * returns TRUE. At all other times, this function returns FALSE. + * + * Side Effects: + * Nodes are removed from the toBeMade queue and job table slots + * are filled. + * + *----------------------------------------------------------------------- + */ + +static int +MakeCheckOrder(void *v_bn, void *ignore MAKE_ATTR_UNUSED) +{ + GNode *bn = v_bn; + + if (bn->made >= MADE || !(bn->flags & REMAKE)) + return 0; + if (DEBUG(MAKE)) + fprintf(debug_file, "MakeCheckOrder: Waiting for .ORDER node %s%s\n", + bn->name, bn->cohort_num); + return 1; +} + +static int +MakeBuildChild(void *v_cn, void *toBeMade_next) +{ + GNode *cn = v_cn; + + if (DEBUG(MAKE)) + fprintf(debug_file, "MakeBuildChild: inspect %s%s, made %d, type %x\n", + cn->name, cn->cohort_num, cn->made, cn->type); + if (cn->made > DEFERRED) + return 0; + + /* If this node is on the RHS of a .ORDER, check LHSs. */ + if (cn->order_pred && Lst_ForEach(cn->order_pred, MakeCheckOrder, 0)) { + /* Can't build this (or anything else in this child list) yet */ + cn->made = DEFERRED; + return 0; /* but keep looking */ + } + + if (DEBUG(MAKE)) + fprintf(debug_file, "MakeBuildChild: schedule %s%s\n", + cn->name, cn->cohort_num); + + cn->made = REQUESTED; + if (toBeMade_next == NULL) + Lst_AtEnd(toBeMade, cn); + else + Lst_InsertBefore(toBeMade, toBeMade_next, cn); + + if (cn->unmade_cohorts != 0) + Lst_ForEach(cn->cohorts, MakeBuildChild, toBeMade_next); + + /* + * If this node is a .WAIT node with unmade chlidren + * then don't add the next sibling. + */ + return cn->type & OP_WAIT && cn->unmade > 0; +} + +/* When a .ORDER LHS node completes we do this on each RHS */ +static int +MakeBuildParent(void *v_pn, void *toBeMade_next) +{ + GNode *pn = v_pn; + + if (pn->made != DEFERRED) + return 0; + + if (MakeBuildChild(pn, toBeMade_next) == 0) { + /* Mark so that when this node is built we reschedule its parents */ + pn->flags |= DONE_ORDER; + } + + return 0; +} + +static Boolean +MakeStartJobs(void) +{ + GNode *gn; + int have_token = 0; + + while (!Lst_IsEmpty (toBeMade)) { + /* Get token now to avoid cycling job-list when we only have 1 token */ + if (!have_token && !Job_TokenWithdraw()) + break; + have_token = 1; + + gn = (GNode *)Lst_DeQueue(toBeMade); + if (DEBUG(MAKE)) + fprintf(debug_file, "Examining %s%s...\n", + gn->name, gn->cohort_num); + + if (gn->made != REQUESTED) { + if (DEBUG(MAKE)) + fprintf(debug_file, "state %d\n", gn->made); + + make_abort(gn, __LINE__); + } + + if (gn->checked == checked) { + /* We've already looked at this node since a job finished... */ + if (DEBUG(MAKE)) + fprintf(debug_file, "already checked %s%s\n", + gn->name, gn->cohort_num); + gn->made = DEFERRED; + continue; + } + gn->checked = checked; + + if (gn->unmade != 0) { + /* + * We can't build this yet, add all unmade children to toBeMade, + * just before the current first element. + */ + gn->made = DEFERRED; + Lst_ForEach(gn->children, MakeBuildChild, Lst_First(toBeMade)); + /* and drop this node on the floor */ + if (DEBUG(MAKE)) + fprintf(debug_file, "dropped %s%s\n", gn->name, gn->cohort_num); + continue; + } + + gn->made = BEINGMADE; + if (Make_OODate(gn)) { + if (DEBUG(MAKE)) { + fprintf(debug_file, "out-of-date\n"); + } + if (queryFlag) { + return (TRUE); + } + Make_DoAllVar(gn); + Job_Make(gn); + have_token = 0; + } else { + if (DEBUG(MAKE)) { + fprintf(debug_file, "up-to-date\n"); + } + gn->made = UPTODATE; + if (gn->type & OP_JOIN) { + /* + * Even for an up-to-date .JOIN node, we need it to have its + * context variables so references to it get the correct + * value for .TARGET when building up the context variables + * of its parent(s)... + */ + Make_DoAllVar(gn); + } + Make_Update(gn); + } + } + + if (have_token) + Job_TokenReturn(); + + return (FALSE); +} + +/*- + *----------------------------------------------------------------------- + * MakePrintStatus -- + * Print the status of a top-level node, viz. it being up-to-date + * already or not created due to an error in a lower level. + * Callback function for Make_Run via Lst_ForEach. + * + * Input: + * gnp Node to examine + * cyclep True if gn->unmade being non-zero implies a + * cycle in the graph, not an error in an + * inferior. + * + * Results: + * Always returns 0. + * + * Side Effects: + * A message may be printed. + * + *----------------------------------------------------------------------- + */ +static int +MakePrintStatusOrder(void *ognp, void *gnp) +{ + GNode *ogn = ognp; + GNode *gn = gnp; + + if (!(ogn->flags & REMAKE) || ogn->made > REQUESTED) + /* not waiting for this one */ + return 0; + + printf(" `%s%s' has .ORDER dependency against %s%s " + "(made %d, flags %x, type %x)\n", + gn->name, gn->cohort_num, + ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); + if (DEBUG(MAKE) && debug_file != stdout) + fprintf(debug_file, " `%s%s' has .ORDER dependency against %s%s " + "(made %d, flags %x, type %x)\n", + gn->name, gn->cohort_num, + ogn->name, ogn->cohort_num, ogn->made, ogn->flags, ogn->type); + return 0; +} + +static int +MakePrintStatus(void *gnp, void *v_errors) +{ + GNode *gn = (GNode *)gnp; + int *errors = v_errors; + + if (gn->flags & DONECYCLE) + /* We've completely processed this node before, don't do it again. */ + return 0; + + if (gn->unmade == 0) { + gn->flags |= DONECYCLE; + switch (gn->made) { + case UPTODATE: + printf("`%s%s' is up to date.\n", gn->name, gn->cohort_num); + break; + case MADE: + break; + case UNMADE: + case DEFERRED: + case REQUESTED: + case BEINGMADE: + (*errors)++; + printf("`%s%s' was not built (made %d, flags %x, type %x)!\n", + gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); + if (DEBUG(MAKE) && debug_file != stdout) + fprintf(debug_file, + "`%s%s' was not built (made %d, flags %x, type %x)!\n", + gn->name, gn->cohort_num, gn->made, gn->flags, gn->type); + /* Most likely problem is actually caused by .ORDER */ + Lst_ForEach(gn->order_pred, MakePrintStatusOrder, gn); + break; + default: + /* Errors - already counted */ + printf("`%s%s' not remade because of errors.\n", + gn->name, gn->cohort_num); + if (DEBUG(MAKE) && debug_file != stdout) + fprintf(debug_file, "`%s%s' not remade because of errors.\n", + gn->name, gn->cohort_num); + break; + } + return 0; + } + + if (DEBUG(MAKE)) + fprintf(debug_file, "MakePrintStatus: %s%s has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); + /* + * If printing cycles and came to one that has unmade children, + * print out the cycle by recursing on its children. + */ + if (!(gn->flags & CYCLE)) { + /* Fist time we've seen this node, check all children */ + gn->flags |= CYCLE; + Lst_ForEach(gn->children, MakePrintStatus, errors); + /* Mark that this node needn't be processed again */ + gn->flags |= DONECYCLE; + return 0; + } + + /* Only output the error once per node */ + gn->flags |= DONECYCLE; + Error("Graph cycles through `%s%s'", gn->name, gn->cohort_num); + if ((*errors)++ > 100) + /* Abandon the whole error report */ + return 1; + + /* Reporting for our children will give the rest of the loop */ + Lst_ForEach(gn->children, MakePrintStatus, errors); + return 0; +} + + +/*- + *----------------------------------------------------------------------- + * Make_ExpandUse -- + * Expand .USE nodes and create a new targets list + * + * Input: + * targs the initial list of targets + * + * Side Effects: + *----------------------------------------------------------------------- + */ +void +Make_ExpandUse(Lst targs) +{ + GNode *gn; /* a temporary pointer */ + Lst examine; /* List of targets to examine */ + + examine = Lst_Duplicate(targs, NULL); + + /* + * Make an initial downward pass over the graph, marking nodes to be made + * as we go down. We call Suff_FindDeps to find where a node is and + * to get some children for it if it has none and also has no commands. + * If the node is a leaf, we stick it on the toBeMade queue to + * be looked at in a minute, otherwise we add its children to our queue + * and go on about our business. + */ + while (!Lst_IsEmpty (examine)) { + gn = (GNode *)Lst_DeQueue(examine); + + if (gn->flags & REMAKE) + /* We've looked at this one already */ + continue; + gn->flags |= REMAKE; + if (DEBUG(MAKE)) + fprintf(debug_file, "Make_ExpandUse: examine %s%s\n", + gn->name, gn->cohort_num); + + if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) { + /* Append all the 'cohorts' to the list of things to examine */ + Lst new; + new = Lst_Duplicate(gn->cohorts, NULL); + Lst_Concat(new, examine, LST_CONCLINK); + examine = new; + } + + /* + * Apply any .USE rules before looking for implicit dependencies + * to make sure everything has commands that should... + * Make sure that the TARGET is set, so that we can make + * expansions. + */ + if (gn->type & OP_ARCHV) { + char *eoa, *eon; + eoa = strchr(gn->name, '('); + eon = strchr(gn->name, ')'); + if (eoa == NULL || eon == NULL) + continue; + *eoa = '\0'; + *eon = '\0'; + Var_Set(MEMBER, eoa + 1, gn, 0); + Var_Set(ARCHIVE, gn->name, gn, 0); + *eoa = '('; + *eon = ')'; + } + + (void)Dir_MTime(gn, 0); + Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); + Lst_ForEach(gn->children, MakeUnmark, gn); + Lst_ForEach(gn->children, MakeHandleUse, gn); + + if ((gn->type & OP_MADE) == 0) + Suff_FindDeps(gn); + else { + /* Pretend we made all this node's children */ + Lst_ForEach(gn->children, MakeFindChild, gn); + if (gn->unmade != 0) + printf("Warning: %s%s still has %d unmade children\n", + gn->name, gn->cohort_num, gn->unmade); + } + + if (gn->unmade != 0) + Lst_ForEach(gn->children, MakeAddChild, examine); + } + + Lst_Destroy(examine, NULL); +} + +/*- + *----------------------------------------------------------------------- + * Make_ProcessWait -- + * Convert .WAIT nodes into dependencies + * + * Input: + * targs the initial list of targets + * + *----------------------------------------------------------------------- + */ + +static int +link_parent(void *cnp, void *pnp) +{ + GNode *cn = cnp; + GNode *pn = pnp; + + Lst_AtEnd(pn->children, cn); + Lst_AtEnd(cn->parents, pn); + pn->unmade++; + return 0; +} + +static int +add_wait_dep(void *v_cn, void *v_wn) +{ + GNode *cn = v_cn; + GNode *wn = v_wn; + + if (cn == wn) + return 1; + + if (cn == NULL || wn == NULL) { + printf("bad wait dep %p %p\n", cn, wn); + exit(4); + } + if (DEBUG(MAKE)) + fprintf(debug_file, ".WAIT: add dependency %s%s -> %s\n", + cn->name, cn->cohort_num, wn->name); + + Lst_AtEnd(wn->children, cn); + wn->unmade++; + Lst_AtEnd(cn->parents, wn); + return 0; +} + +static void +Make_ProcessWait(Lst targs) +{ + GNode *pgn; /* 'parent' node we are examining */ + GNode *cgn; /* Each child in turn */ + LstNode owln; /* Previous .WAIT node */ + Lst examine; /* List of targets to examine */ + LstNode ln; + + /* + * We need all the nodes to have a common parent in order for the + * .WAIT and .ORDER scheduling to work. + * Perhaps this should be done earlier... + */ + + pgn = Targ_NewGN(".MAIN"); + pgn->flags = REMAKE; + pgn->type = OP_PHONY | OP_DEPENDS; + /* Get it displayed in the diag dumps */ + Lst_AtFront(Targ_List(), pgn); + + Lst_ForEach(targs, link_parent, pgn); + + /* Start building with the 'dummy' .MAIN' node */ + MakeBuildChild(pgn, NULL); + + examine = Lst_Init(FALSE); + Lst_AtEnd(examine, pgn); + + while (!Lst_IsEmpty (examine)) { + pgn = Lst_DeQueue(examine); + + /* We only want to process each child-list once */ + if (pgn->flags & DONE_WAIT) + continue; + pgn->flags |= DONE_WAIT; + if (DEBUG(MAKE)) + fprintf(debug_file, "Make_ProcessWait: examine %s\n", pgn->name); + + if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) { + /* Append all the 'cohorts' to the list of things to examine */ + Lst new; + new = Lst_Duplicate(pgn->cohorts, NULL); + Lst_Concat(new, examine, LST_CONCLINK); + examine = new; + } + + owln = Lst_First(pgn->children); + Lst_Open(pgn->children); + for (; (ln = Lst_Next(pgn->children)) != NULL; ) { + cgn = Lst_Datum(ln); + if (cgn->type & OP_WAIT) { + /* Make the .WAIT node depend on the previous children */ + Lst_ForEachFrom(pgn->children, owln, add_wait_dep, cgn); + owln = ln; + } else { + Lst_AtEnd(examine, cgn); + } + } + Lst_Close(pgn->children); + } + + Lst_Destroy(examine, NULL); +} + +/*- + *----------------------------------------------------------------------- + * Make_Run -- + * Initialize the nodes to remake and the list of nodes which are + * ready to be made by doing a breadth-first traversal of the graph + * starting from the nodes in the given list. Once this traversal + * is finished, all the 'leaves' of the graph are in the toBeMade + * queue. + * Using this queue and the Job module, work back up the graph, + * calling on MakeStartJobs to keep the job table as full as + * possible. + * + * Input: + * targs the initial list of targets + * + * Results: + * TRUE if work was done. FALSE otherwise. + * + * Side Effects: + * The make field of all nodes involved in the creation of the given + * targets is set to 1. The toBeMade list is set to contain all the + * 'leaves' of these subgraphs. + *----------------------------------------------------------------------- + */ +Boolean +Make_Run(Lst targs) +{ + int errors; /* Number of errors the Job module reports */ + + /* Start trying to make the current targets... */ + toBeMade = Lst_Init(FALSE); + + Make_ExpandUse(targs); + Make_ProcessWait(targs); + + if (DEBUG(MAKE)) { + fprintf(debug_file, "#***# full graph\n"); + Targ_PrintGraph(1); + } + + if (queryFlag) { + /* + * We wouldn't do any work unless we could start some jobs in the + * next loop... (we won't actually start any, of course, this is just + * to see if any of the targets was out of date) + */ + return (MakeStartJobs()); + } + /* + * Initialization. At the moment, no jobs are running and until some + * get started, nothing will happen since the remaining upward + * traversal of the graph is performed by the routines in job.c upon + * the finishing of a job. So we fill the Job table as much as we can + * before going into our loop. + */ + (void)MakeStartJobs(); + + /* + * Main Loop: The idea here is that the ending of jobs will take + * care of the maintenance of data structures and the waiting for output + * will cause us to be idle most of the time while our children run as + * much as possible. Because the job table is kept as full as possible, + * the only time when it will be empty is when all the jobs which need + * running have been run, so that is the end condition of this loop. + * Note that the Job module will exit if there were any errors unless the + * keepgoing flag was given. + */ + while (!Lst_IsEmpty(toBeMade) || jobTokensRunning > 0) { + Job_CatchOutput(); + (void)MakeStartJobs(); + } + + errors = Job_Finish(); + + /* + * Print the final status of each target. E.g. if it wasn't made + * because some inferior reported an error. + */ + if (DEBUG(MAKE)) + fprintf(debug_file, "done: errors %d\n", errors); + if (errors == 0) { + Lst_ForEach(targs, MakePrintStatus, &errors); + if (DEBUG(MAKE)) { + fprintf(debug_file, "done: errors %d\n", errors); + if (errors) + Targ_PrintGraph(4); + } + } + return errors != 0; +} diff --git a/usr.bin/make/make.h b/usr.bin/make/make.h new file mode 100644 index 0000000..53e1895 --- /dev/null +++ b/usr.bin/make/make.h @@ -0,0 +1,535 @@ +/* $NetBSD: make.h,v 1.104 2018/02/12 21:38:09 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)make.h 8.3 (Berkeley) 6/13/95 + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)make.h 8.3 (Berkeley) 6/13/95 + */ + +/*- + * make.h -- + * The global definitions for pmake + */ + +#ifndef _MAKE_H_ +#define _MAKE_H_ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef BSD4_4 +# include +#endif + +#ifndef FD_CLOEXEC +#define FD_CLOEXEC 1 +#endif + +#if defined(__GNUC__) +#define MAKE_GNUC_PREREQ(x, y) \ + ((__GNUC__ == (x) && __GNUC_MINOR__ >= (y)) || \ + (__GNUC__ > (x))) +#else /* defined(__GNUC__) */ +#define MAKE_GNUC_PREREQ(x, y) 0 +#endif /* defined(__GNUC__) */ + +#if MAKE_GNUC_PREREQ(2, 7) +#define MAKE_ATTR_UNUSED __attribute__((__unused__)) +#else +#define MAKE_ATTR_UNUSED /* delete */ +#endif + +#if MAKE_GNUC_PREREQ(2, 5) +#define MAKE_ATTR_DEAD __attribute__((__noreturn__)) +#elif defined(__GNUC__) +#define MAKE_ATTR_DEAD __volatile +#else +#define MAKE_ATTR_DEAD /* delete */ +#endif + +#if MAKE_GNUC_PREREQ(2, 7) +#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) \ + __attribute__((__format__ (__printf__, fmtarg, firstvararg))) +#else +#define MAKE_ATTR_PRINTFLIKE(fmtarg, firstvararg) /* delete */ +#endif + +#include "sprite.h" +#include "lst.h" +#include "hash.h" +#include "config.h" +#include "buf.h" +#include "make_malloc.h" + +/*- + * The structure for an individual graph node. Each node has several + * pieces of data associated with it. + * 1) the name of the target it describes + * 2) the location of the target file in the file system. + * 3) the type of operator used to define its sources (qv. parse.c) + * 4) whether it is involved in this invocation of make + * 5) whether the target has been remade + * 6) whether any of its children has been remade + * 7) the number of its children that are, as yet, unmade + * 8) its modification time + * 9) the modification time of its youngest child (qv. make.c) + * 10) a list of nodes for which this is a source (parents) + * 11) a list of nodes on which this depends (children) + * 12) a list of nodes that depend on this, as gleaned from the + * transformation rules (iParents) + * 13) a list of ancestor nodes, which includes parents, iParents, + * and recursive parents of parents + * 14) a list of nodes of the same name created by the :: operator + * 15) a list of nodes that must be made (if they're made) before + * this node can be, but that do not enter into the datedness of + * this node. + * 16) a list of nodes that must be made (if they're made) before + * this node or any child of this node can be, but that do not + * enter into the datedness of this node. + * 17) a list of nodes that must be made (if they're made) after + * this node is, but that do not depend on this node, in the + * normal sense. + * 18) a Lst of ``local'' variables that are specific to this target + * and this target only (qv. var.c [$@ $< $?, etc.]) + * 19) a Lst of strings that are commands to be given to a shell + * to create this target. + */ +typedef struct GNode { + char *name; /* The target's name */ + char *uname; /* The unexpanded name of a .USE node */ + char *path; /* The full pathname of the file */ + int type; /* Its type (see the OP flags, below) */ + + int flags; +#define REMAKE 0x1 /* this target needs to be (re)made */ +#define CHILDMADE 0x2 /* children of this target were made */ +#define FORCE 0x4 /* children don't exist, and we pretend made */ +#define DONE_WAIT 0x8 /* Set by Make_ProcessWait() */ +#define DONE_ORDER 0x10 /* Build requested by .ORDER processing */ +#define FROM_DEPEND 0x20 /* Node created from .depend */ +#define DONE_ALLSRC 0x40 /* We do it once only */ +#define CYCLE 0x1000 /* Used by MakePrintStatus */ +#define DONECYCLE 0x2000 /* Used by MakePrintStatus */ +#define INTERNAL 0x4000 /* Internal use only */ + enum enum_made { + UNMADE, DEFERRED, REQUESTED, BEINGMADE, + MADE, UPTODATE, ERROR, ABORTED + } made; /* Set to reflect the state of processing + * on this node: + * UNMADE - Not examined yet + * DEFERRED - Examined once (building child) + * REQUESTED - on toBeMade list + * BEINGMADE - Target is already being made. + * Indicates a cycle in the graph. + * MADE - Was out-of-date and has been made + * UPTODATE - Was already up-to-date + * ERROR - An error occurred while it was being + * made (used only in compat mode) + * ABORTED - The target was aborted due to + * an error making an inferior (compat). + */ + int unmade; /* The number of unmade children */ + + time_t mtime; /* Its modification time */ + struct GNode *cmgn; /* The youngest child */ + + Lst iParents; /* Links to parents for which this is an + * implied source, if any */ + Lst cohorts; /* Other nodes for the :: operator */ + Lst parents; /* Nodes that depend on this one */ + Lst children; /* Nodes on which this one depends */ + Lst order_pred; /* .ORDER nodes we need made */ + Lst order_succ; /* .ORDER nodes who need us */ + + char cohort_num[8]; /* #n for this cohort */ + int unmade_cohorts;/* # of unmade instances on the + cohorts list */ + struct GNode *centurion; /* Pointer to the first instance of a :: + node; only set when on a cohorts list */ + unsigned int checked; /* Last time we tried to makle this node */ + + Hash_Table context; /* The local variables */ + Lst commands; /* Creation commands */ + + struct _Suff *suffix; /* Suffix for the node (determined by + * Suff_FindDeps and opaque to everyone + * but the Suff module) */ + const char *fname; /* filename where the GNode got defined */ + int lineno; /* line number where the GNode got defined */ +} GNode; + +/* + * The OP_ constants are used when parsing a dependency line as a way of + * communicating to other parts of the program the way in which a target + * should be made. These constants are bitwise-OR'ed together and + * placed in the 'type' field of each node. Any node that has + * a 'type' field which satisfies the OP_NOP function was never never on + * the lefthand side of an operator, though it may have been on the + * righthand side... + */ +#define OP_DEPENDS 0x00000001 /* Execution of commands depends on + * kids (:) */ +#define OP_FORCE 0x00000002 /* Always execute commands (!) */ +#define OP_DOUBLEDEP 0x00000004 /* Execution of commands depends on kids + * per line (::) */ +#define OP_OPMASK (OP_DEPENDS|OP_FORCE|OP_DOUBLEDEP) + +#define OP_OPTIONAL 0x00000008 /* Don't care if the target doesn't + * exist and can't be created */ +#define OP_USE 0x00000010 /* Use associated commands for parents */ +#define OP_EXEC 0x00000020 /* Target is never out of date, but always + * execute commands anyway. Its time + * doesn't matter, so it has none...sort + * of */ +#define OP_IGNORE 0x00000040 /* Ignore errors when creating the node */ +#define OP_PRECIOUS 0x00000080 /* Don't remove the target when + * interrupted */ +#define OP_SILENT 0x00000100 /* Don't echo commands when executed */ +#define OP_MAKE 0x00000200 /* Target is a recursive make so its + * commands should always be executed when + * it is out of date, regardless of the + * state of the -n or -t flags */ +#define OP_JOIN 0x00000400 /* Target is out-of-date only if any of its + * children was out-of-date */ +#define OP_MADE 0x00000800 /* Assume the children of the node have + * been already made */ +#define OP_SPECIAL 0x00001000 /* Special .BEGIN, .END, .INTERRUPT */ +#define OP_USEBEFORE 0x00002000 /* Like .USE, only prepend commands */ +#define OP_INVISIBLE 0x00004000 /* The node is invisible to its parents. + * I.e. it doesn't show up in the parents's + * local variables. */ +#define OP_NOTMAIN 0x00008000 /* The node is exempt from normal 'main + * target' processing in parse.c */ +#define OP_PHONY 0x00010000 /* Not a file target; run always */ +#define OP_NOPATH 0x00020000 /* Don't search for file in the path */ +#define OP_WAIT 0x00040000 /* .WAIT phony node */ +#define OP_NOMETA 0x00080000 /* .NOMETA do not create a .meta file */ +#define OP_META 0x00100000 /* .META we _do_ want a .meta file */ +#define OP_NOMETA_CMP 0x00200000 /* Do not compare commands in .meta file */ +#define OP_SUBMAKE 0x00400000 /* Possibly a submake node */ +/* Attributes applied by PMake */ +#define OP_TRANSFORM 0x80000000 /* The node is a transformation rule */ +#define OP_MEMBER 0x40000000 /* Target is a member of an archive */ +#define OP_LIB 0x20000000 /* Target is a library */ +#define OP_ARCHV 0x10000000 /* Target is an archive construct */ +#define OP_HAS_COMMANDS 0x08000000 /* Target has all the commands it should. + * Used when parsing to catch multiple + * commands for a target */ +#define OP_SAVE_CMDS 0x04000000 /* Saving commands on .END (Compat) */ +#define OP_DEPS_FOUND 0x02000000 /* Already processed by Suff_FindDeps */ +#define OP_MARK 0x01000000 /* Node found while expanding .ALLSRC */ + +#define NoExecute(gn) ((gn->type & OP_MAKE) ? noRecursiveExecute : noExecute) +/* + * OP_NOP will return TRUE if the node with the given type was not the + * object of a dependency operator + */ +#define OP_NOP(t) (((t) & OP_OPMASK) == 0x00000000) + +#define OP_NOTARGET (OP_NOTMAIN|OP_USE|OP_EXEC|OP_TRANSFORM) + +/* + * The TARG_ constants are used when calling the Targ_FindNode and + * Targ_FindList functions in targ.c. They simply tell the functions what to + * do if the desired node(s) is (are) not found. If the TARG_CREATE constant + * is given, a new, empty node will be created for the target, placed in the + * table of all targets and its address returned. If TARG_NOCREATE is given, + * a NULL pointer will be returned. + */ +#define TARG_NOCREATE 0x00 /* don't create it */ +#define TARG_CREATE 0x01 /* create node if not found */ +#define TARG_NOHASH 0x02 /* don't look in/add to hash table */ + +/* + * These constants are all used by the Str_Concat function to decide how the + * final string should look. If STR_ADDSPACE is given, a space will be + * placed between the two strings. If STR_ADDSLASH is given, a '/' will + * be used instead of a space. If neither is given, no intervening characters + * will be placed between the two strings in the final output. If the + * STR_DOFREE bit is set, the two input strings will be freed before + * Str_Concat returns. + */ +#define STR_ADDSPACE 0x01 /* add a space when Str_Concat'ing */ +#define STR_ADDSLASH 0x02 /* add a slash when Str_Concat'ing */ + +/* + * Error levels for parsing. PARSE_FATAL means the process cannot continue + * once the makefile has been parsed. PARSE_WARNING means it can. Passed + * as the first argument to Parse_Error. + */ +#define PARSE_INFO 3 +#define PARSE_WARNING 2 +#define PARSE_FATAL 1 + +/* + * Values returned by Cond_Eval. + */ +#define COND_PARSE 0 /* Parse the next lines */ +#define COND_SKIP 1 /* Skip the next lines */ +#define COND_INVALID 2 /* Not a conditional statement */ + +/* + * Definitions for the "local" variables. Used only for clarity. + */ +#define TARGET "@" /* Target of dependency */ +#define OODATE "?" /* All out-of-date sources */ +#define ALLSRC ">" /* All sources */ +#define IMPSRC "<" /* Source implied by transformation */ +#define PREFIX "*" /* Common prefix */ +#define ARCHIVE "!" /* Archive in "archive(member)" syntax */ +#define MEMBER "%" /* Member in "archive(member)" syntax */ + +#define FTARGET "@F" /* file part of TARGET */ +#define DTARGET "@D" /* directory part of TARGET */ +#define FIMPSRC " b) ? a : b) +#endif + +/* At least GNU/Hurd systems lack hardcoded MAXPATHLEN/PATH_MAX */ +#include +#ifndef MAXPATHLEN +#define MAXPATHLEN 4096 +#endif +#ifndef PATH_MAX +#define PATH_MAX MAXPATHLEN +#endif + +#if defined(SYSV) +#define KILLPG(pid, sig) kill(-(pid), (sig)) +#else +#define KILLPG(pid, sig) killpg((pid), (sig)) +#endif + +#endif /* _MAKE_H_ */ diff --git a/usr.bin/make/make_malloc.c b/usr.bin/make/make_malloc.c new file mode 100644 index 0000000..035d519 --- /dev/null +++ b/usr.bin/make/make_malloc.c @@ -0,0 +1,119 @@ +/* $NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $ */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifdef MAKE_NATIVE +#include +__RCSID("$NetBSD: make_malloc.c,v 1.11 2017/04/16 20:20:24 dholland Exp $"); +#endif + +#include +#include +#include +#include + +#include "make.h" + +#ifndef USE_EMALLOC +static MAKE_ATTR_DEAD void enomem(void); + +/* + * enomem -- + * die when out of memory. + */ +static MAKE_ATTR_DEAD void +enomem(void) +{ + (void)fprintf(stderr, "%s: %s.\n", progname, strerror(ENOMEM)); + exit(2); +} + +/* + * bmake_malloc -- + * malloc, but die on error. + */ +void * +bmake_malloc(size_t len) +{ + void *p; + + if ((p = malloc(len)) == NULL) + enomem(); + return(p); +} + +/* + * bmake_strdup -- + * strdup, but die on error. + */ +char * +bmake_strdup(const char *str) +{ + size_t len; + char *p; + + len = strlen(str) + 1; + if ((p = malloc(len)) == NULL) + enomem(); + return memcpy(p, str, len); +} + +/* + * bmake_strndup -- + * strndup, but die on error. + */ +char * +bmake_strndup(const char *str, size_t max_len) +{ + size_t len; + char *p; + + if (str == NULL) + return NULL; + + len = strlen(str); + if (len > max_len) + len = max_len; + p = bmake_malloc(len + 1); + memcpy(p, str, len); + p[len] = '\0'; + + return(p); +} + +/* + * bmake_realloc -- + * realloc, but die on error. + */ +void * +bmake_realloc(void *ptr, size_t size) +{ + if ((ptr = realloc(ptr, size)) == NULL) + enomem(); + return(ptr); +} +#endif diff --git a/usr.bin/make/make_malloc.h b/usr.bin/make/make_malloc.h new file mode 100644 index 0000000..36d3eff --- /dev/null +++ b/usr.bin/make/make_malloc.h @@ -0,0 +1,41 @@ +/* $NetBSD: make_malloc.h,v 1.4 2009/01/24 14:43:29 dsl Exp $ */ + +/*- + * Copyright (c) 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef USE_EMALLOC +void *bmake_malloc(size_t); +void *bmake_realloc(void *, size_t); +char *bmake_strdup(const char *); +char *bmake_strndup(const char *, size_t); +#else +#include +#define bmake_malloc(x) emalloc(x) +#define bmake_realloc(x,y) erealloc(x,y) +#define bmake_strdup(x) estrdup(x) +#define bmake_strndup(x,y) estrndup(x,y) +#endif + diff --git a/usr.bin/make/meta.c b/usr.bin/make/meta.c new file mode 100644 index 0000000..4a3f41d --- /dev/null +++ b/usr.bin/make/meta.c @@ -0,0 +1,1641 @@ +/* $NetBSD: meta.c,v 1.70 2018/02/13 19:37:30 sjg Exp $ */ + +/* + * Implement 'meta' mode. + * Adapted from John Birrell's patches to FreeBSD make. + * --sjg + */ +/* + * Copyright (c) 2009-2016, Juniper Networks, Inc. + * Portions Copyright (c) 2009, John Birrell. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#if defined(USE_META) + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#if !defined(HAVE_CONFIG_H) || defined(HAVE_ERR_H) +#include +#endif + +#include "make.h" +#include "job.h" + +#ifdef HAVE_FILEMON_H +# include +#endif +#if !defined(USE_FILEMON) && defined(FILEMON_SET_FD) +# define USE_FILEMON +#endif + +static BuildMon Mybm; /* for compat */ +static Lst metaBailiwick; /* our scope of control */ +static char *metaBailiwickStr; /* string storage for the list */ +static Lst metaIgnorePaths; /* paths we deliberately ignore */ +static char *metaIgnorePathsStr; /* string storage for the list */ + +#ifndef MAKE_META_IGNORE_PATHS +#define MAKE_META_IGNORE_PATHS ".MAKE.META.IGNORE_PATHS" +#endif +#ifndef MAKE_META_IGNORE_PATTERNS +#define MAKE_META_IGNORE_PATTERNS ".MAKE.META.IGNORE_PATTERNS" +#endif +#ifndef MAKE_META_IGNORE_FILTER +#define MAKE_META_IGNORE_FILTER ".MAKE.META.IGNORE_FILTER" +#endif + +Boolean useMeta = FALSE; +static Boolean useFilemon = FALSE; +static Boolean writeMeta = FALSE; +static Boolean metaMissing = FALSE; /* oodate if missing */ +static Boolean filemonMissing = FALSE; /* oodate if missing */ +static Boolean metaEnv = FALSE; /* don't save env unless asked */ +static Boolean metaVerbose = FALSE; +static Boolean metaIgnoreCMDs = FALSE; /* ignore CMDs in .meta files */ +static Boolean metaIgnorePatterns = FALSE; /* do we need to do pattern matches */ +static Boolean metaIgnoreFilter = FALSE; /* do we have more complex filtering? */ +static Boolean metaCurdirOk = FALSE; /* write .meta in .CURDIR Ok? */ +static Boolean metaSilent = FALSE; /* if we have a .meta be SILENT */ + +extern Boolean forceJobs; +extern Boolean comatMake; +extern char **environ; + +#define MAKE_META_PREFIX ".MAKE.META.PREFIX" + +#ifndef N2U +# define N2U(n, u) (((n) + ((u) - 1)) / (u)) +#endif +#ifndef ROUNDUP +# define ROUNDUP(n, u) (N2U((n), (u)) * (u)) +#endif + +#if !defined(HAVE_STRSEP) +# define strsep(s, d) stresep((s), (d), 0) +#endif + +/* + * Filemon is a kernel module which snoops certain syscalls. + * + * C chdir + * E exec + * F [v]fork + * L [sym]link + * M rename + * R read + * W write + * S stat + * + * See meta_oodate below - we mainly care about 'E' and 'R'. + * + * We can still use meta mode without filemon, but + * the benefits are more limited. + */ +#ifdef USE_FILEMON +# ifndef _PATH_FILEMON +# define _PATH_FILEMON "/dev/filemon" +# endif + +/* + * Open the filemon device. + */ +static void +filemon_open(BuildMon *pbm) +{ + int retry; + + pbm->mon_fd = pbm->filemon_fd = -1; + if (!useFilemon) + return; + + for (retry = 5; retry >= 0; retry--) { + if ((pbm->filemon_fd = open(_PATH_FILEMON, O_RDWR)) >= 0) + break; + } + + if (pbm->filemon_fd < 0) { + useFilemon = FALSE; + warn("Could not open %s", _PATH_FILEMON); + return; + } + + /* + * We use a file outside of '.' + * to avoid a FreeBSD kernel bug where unlink invalidates + * cwd causing getcwd to do a lot more work. + * We only care about the descriptor. + */ + pbm->mon_fd = mkTempFile("filemon.XXXXXX", NULL); + if (ioctl(pbm->filemon_fd, FILEMON_SET_FD, &pbm->mon_fd) < 0) { + err(1, "Could not set filemon file descriptor!"); + } + /* we don't need these once we exec */ + (void)fcntl(pbm->mon_fd, F_SETFD, FD_CLOEXEC); + (void)fcntl(pbm->filemon_fd, F_SETFD, FD_CLOEXEC); +} + +/* + * Read the build monitor output file and write records to the target's + * metadata file. + */ +static int +filemon_read(FILE *mfp, int fd) +{ + char buf[BUFSIZ]; + int n; + int error; + + /* Check if we're not writing to a meta data file.*/ + if (mfp == NULL) { + if (fd >= 0) + close(fd); /* not interested */ + return 0; + } + /* rewind */ + (void)lseek(fd, (off_t)0, SEEK_SET); + + error = 0; + fprintf(mfp, "\n-- filemon acquired metadata --\n"); + + while ((n = read(fd, buf, sizeof(buf))) > 0) { + if ((int)fwrite(buf, 1, n, mfp) < n) + error = EIO; + } + fflush(mfp); + if (close(fd) < 0) + error = errno; + return error; +} +#endif + +/* + * when realpath() fails, + * we use this, to clean up ./ and ../ + */ +static void +eat_dots(char *buf, size_t bufsz, int dots) +{ + char *cp; + char *cp2; + const char *eat; + size_t eatlen; + + switch (dots) { + case 1: + eat = "/./"; + eatlen = 2; + break; + case 2: + eat = "/../"; + eatlen = 3; + break; + default: + return; + } + + do { + cp = strstr(buf, eat); + if (cp) { + cp2 = cp + eatlen; + if (dots == 2 && cp > buf) { + do { + cp--; + } while (cp > buf && *cp != '/'); + } + if (*cp == '/') { + strlcpy(cp, cp2, bufsz - (cp - buf)); + } else { + return; /* can't happen? */ + } + } + } while (cp); +} + +static char * +meta_name(struct GNode *gn, char *mname, size_t mnamelen, + const char *dname, + const char *tname, + const char *cwd) +{ + char buf[MAXPATHLEN]; + char *rp; + char *cp; + char *tp; + char *dtp; + size_t ldname; + + /* + * Weed out relative paths from the target file name. + * We have to be careful though since if target is a + * symlink, the result will be unstable. + * So we use realpath() just to get the dirname, and leave the + * basename as given to us. + */ + if ((cp = strrchr(tname, '/'))) { + if (cached_realpath(tname, buf)) { + if ((rp = strrchr(buf, '/'))) { + rp++; + cp++; + if (strcmp(cp, rp) != 0) + strlcpy(rp, cp, sizeof(buf) - (rp - buf)); + } + tname = buf; + } else { + /* + * We likely have a directory which is about to be made. + * We pretend realpath() succeeded, to have a chance + * of generating the same meta file name that we will + * next time through. + */ + if (tname[0] == '/') { + strlcpy(buf, tname, sizeof(buf)); + } else { + snprintf(buf, sizeof(buf), "%s/%s", cwd, tname); + } + eat_dots(buf, sizeof(buf), 1); /* ./ */ + eat_dots(buf, sizeof(buf), 2); /* ../ */ + tname = buf; + } + } + /* on some systems dirname may modify its arg */ + tp = bmake_strdup(tname); + dtp = dirname(tp); + if (strcmp(dname, dtp) == 0) + snprintf(mname, mnamelen, "%s.meta", tname); + else { + ldname = strlen(dname); + if (strncmp(dname, dtp, ldname) == 0 && dtp[ldname] == '/') + snprintf(mname, mnamelen, "%s/%s.meta", dname, &tname[ldname+1]); + else + snprintf(mname, mnamelen, "%s/%s.meta", dname, tname); + + /* + * Replace path separators in the file name after the + * current object directory path. + */ + cp = mname + strlen(dname) + 1; + + while (*cp != '\0') { + if (*cp == '/') + *cp = '_'; + cp++; + } + } + free(tp); + return (mname); +} + +/* + * Return true if running ${.MAKE} + * Bypassed if target is flagged .MAKE + */ +static int +is_submake(void *cmdp, void *gnp) +{ + static char *p_make = NULL; + static int p_len; + char *cmd = cmdp; + GNode *gn = gnp; + char *mp = NULL; + char *cp; + char *cp2; + int rc = 0; /* keep looking */ + + if (!p_make) { + p_make = Var_Value(".MAKE", gn, &cp); + p_len = strlen(p_make); + } + cp = strchr(cmd, '$'); + if ((cp)) { + mp = Var_Subst(NULL, cmd, gn, VARF_WANTRES); + cmd = mp; + } + cp2 = strstr(cmd, p_make); + if ((cp2)) { + switch (cp2[p_len]) { + case '\0': + case ' ': + case '\t': + case '\n': + rc = 1; + break; + } + if (cp2 > cmd && rc > 0) { + switch (cp2[-1]) { + case ' ': + case '\t': + case '\n': + break; + default: + rc = 0; /* no match */ + break; + } + } + } + free(mp); + return (rc); +} + +typedef struct meta_file_s { + FILE *fp; + GNode *gn; +} meta_file_t; + +static int +printCMD(void *cmdp, void *mfpp) +{ + meta_file_t *mfp = mfpp; + char *cmd = cmdp; + char *cp = NULL; + + if (strchr(cmd, '$')) { + cmd = cp = Var_Subst(NULL, cmd, mfp->gn, VARF_WANTRES); + } + fprintf(mfp->fp, "CMD %s\n", cmd); + free(cp); + return 0; +} + +/* + * Certain node types never get a .meta file + */ +#define SKIP_META_TYPE(_type) do { \ + if ((gn->type & __CONCAT(OP_, _type))) { \ + if (verbose) { \ + fprintf(debug_file, "Skipping meta for %s: .%s\n", \ + gn->name, __STRING(_type)); \ + } \ + return FALSE; \ + } \ +} while (0) + + +/* + * Do we need/want a .meta file ? + */ +static Boolean +meta_needed(GNode *gn, const char *dname, const char *tname, + char *objdir, int verbose) +{ + struct stat fs; + + if (verbose) + verbose = DEBUG(META); + + /* This may be a phony node which we don't want meta data for... */ + /* Skip .meta for .BEGIN, .END, .ERROR etc as well. */ + /* Or it may be explicitly flagged as .NOMETA */ + SKIP_META_TYPE(NOMETA); + /* Unless it is explicitly flagged as .META */ + if (!(gn->type & OP_META)) { + SKIP_META_TYPE(PHONY); + SKIP_META_TYPE(SPECIAL); + SKIP_META_TYPE(MAKE); + } + + /* Check if there are no commands to execute. */ + if (Lst_IsEmpty(gn->commands)) { + if (verbose) + fprintf(debug_file, "Skipping meta for %s: no commands\n", + gn->name); + return FALSE; + } + if ((gn->type & (OP_META|OP_SUBMAKE)) == OP_SUBMAKE) { + /* OP_SUBMAKE is a bit too aggressive */ + if (Lst_ForEach(gn->commands, is_submake, gn)) { + if (DEBUG(META)) + fprintf(debug_file, "Skipping meta for %s: .SUBMAKE\n", + gn->name); + return FALSE; + } + } + + /* The object directory may not exist. Check it.. */ + if (cached_stat(dname, &fs) != 0) { + if (verbose) + fprintf(debug_file, "Skipping meta for %s: no .OBJDIR\n", + gn->name); + return FALSE; + } + + /* make sure these are canonical */ + if (cached_realpath(dname, objdir)) + dname = objdir; + + /* If we aren't in the object directory, don't create a meta file. */ + if (!metaCurdirOk && strcmp(curdir, dname) == 0) { + if (verbose) + fprintf(debug_file, "Skipping meta for %s: .OBJDIR == .CURDIR\n", + gn->name); + return FALSE; + } + return TRUE; +} + + +static FILE * +meta_create(BuildMon *pbm, GNode *gn) +{ + meta_file_t mf; + char buf[MAXPATHLEN]; + char objdir[MAXPATHLEN]; + char **ptr; + const char *dname; + const char *tname; + char *fname; + const char *cp; + char *p[4]; /* >= possible uses */ + int i; + + mf.fp = NULL; + i = 0; + + dname = Var_Value(".OBJDIR", gn, &p[i++]); + tname = Var_Value(TARGET, gn, &p[i++]); + + /* if this succeeds objdir is realpath of dname */ + if (!meta_needed(gn, dname, tname, objdir, TRUE)) + goto out; + dname = objdir; + + if (metaVerbose) { + char *mp; + + /* Describe the target we are building */ + mp = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", gn, VARF_WANTRES); + if (*mp) + fprintf(stdout, "%s\n", mp); + free(mp); + } + /* Get the basename of the target */ + if ((cp = strrchr(tname, '/')) == NULL) { + cp = tname; + } else { + cp++; + } + + fflush(stdout); + + if (!writeMeta) + /* Don't create meta data. */ + goto out; + + fname = meta_name(gn, pbm->meta_fname, sizeof(pbm->meta_fname), + dname, tname, objdir); + +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_create: %s\n", fname); +#endif + + if ((mf.fp = fopen(fname, "w")) == NULL) + err(1, "Could not open meta file '%s'", fname); + + fprintf(mf.fp, "# Meta data file %s\n", fname); + + mf.gn = gn; + + Lst_ForEach(gn->commands, printCMD, &mf); + + fprintf(mf.fp, "CWD %s\n", getcwd(buf, sizeof(buf))); + fprintf(mf.fp, "TARGET %s\n", tname); + + if (metaEnv) { + for (ptr = environ; *ptr != NULL; ptr++) + fprintf(mf.fp, "ENV %s\n", *ptr); + } + + fprintf(mf.fp, "-- command output --\n"); + fflush(mf.fp); + + Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); + Var_Append(".MAKE.META.CREATED", fname, VAR_GLOBAL); + + gn->type |= OP_META; /* in case anyone wants to know */ + if (metaSilent) { + gn->type |= OP_SILENT; + } + out: + for (i--; i >= 0; i--) { + free(p[i]); + } + + return (mf.fp); +} + +static Boolean +boolValue(char *s) +{ + switch(*s) { + case '0': + case 'N': + case 'n': + case 'F': + case 'f': + return FALSE; + } + return TRUE; +} + +/* + * Initialization we need before reading makefiles. + */ +void +meta_init(void) +{ +#ifdef USE_FILEMON + /* this allows makefiles to test if we have filemon support */ + Var_Set(".MAKE.PATH_FILEMON", _PATH_FILEMON, VAR_GLOBAL, 0); +#endif +} + + +#define get_mode_bf(bf, token) \ + if ((cp = strstr(make_mode, token))) \ + bf = boolValue(&cp[sizeof(token) - 1]) + +/* + * Initialization we need after reading makefiles. + */ +void +meta_mode_init(const char *make_mode) +{ + static int once = 0; + char *cp; + + useMeta = TRUE; + useFilemon = TRUE; + writeMeta = TRUE; + + if (make_mode) { + if (strstr(make_mode, "env")) + metaEnv = TRUE; + if (strstr(make_mode, "verb")) + metaVerbose = TRUE; + if (strstr(make_mode, "read")) + writeMeta = FALSE; + if (strstr(make_mode, "nofilemon")) + useFilemon = FALSE; + if (strstr(make_mode, "ignore-cmd")) + metaIgnoreCMDs = TRUE; + if (useFilemon) + get_mode_bf(filemonMissing, "missing-filemon="); + get_mode_bf(metaCurdirOk, "curdirok="); + get_mode_bf(metaMissing, "missing-meta="); + get_mode_bf(metaSilent, "silent="); + } + if (metaVerbose && !Var_Exists(MAKE_META_PREFIX, VAR_GLOBAL)) { + /* + * The default value for MAKE_META_PREFIX + * prints the absolute path of the target. + * This works be cause :H will generate '.' if there is no / + * and :tA will resolve that to cwd. + */ + Var_Set(MAKE_META_PREFIX, "Building ${.TARGET:H:tA}/${.TARGET:T}", VAR_GLOBAL, 0); + } + if (once) + return; + once = 1; + memset(&Mybm, 0, sizeof(Mybm)); + /* + * We consider ourselves master of all within ${.MAKE.META.BAILIWICK} + */ + metaBailiwick = Lst_Init(FALSE); + metaBailiwickStr = Var_Subst(NULL, "${.MAKE.META.BAILIWICK:O:u:tA}", + VAR_GLOBAL, VARF_WANTRES); + if (metaBailiwickStr) { + str2Lst_Append(metaBailiwick, metaBailiwickStr, NULL); + } + /* + * We ignore any paths that start with ${.MAKE.META.IGNORE_PATHS} + */ + metaIgnorePaths = Lst_Init(FALSE); + Var_Append(MAKE_META_IGNORE_PATHS, + "/dev /etc /proc /tmp /var/run /var/tmp ${TMPDIR}", VAR_GLOBAL); + metaIgnorePathsStr = Var_Subst(NULL, + "${" MAKE_META_IGNORE_PATHS ":O:u:tA}", VAR_GLOBAL, + VARF_WANTRES); + if (metaIgnorePathsStr) { + str2Lst_Append(metaIgnorePaths, metaIgnorePathsStr, NULL); + } + + /* + * We ignore any paths that match ${.MAKE.META.IGNORE_PATTERNS} + */ + cp = NULL; + if (Var_Value(MAKE_META_IGNORE_PATTERNS, VAR_GLOBAL, &cp)) { + metaIgnorePatterns = TRUE; + free(cp); + } + cp = NULL; + if (Var_Value(MAKE_META_IGNORE_FILTER, VAR_GLOBAL, &cp)) { + metaIgnoreFilter = TRUE; + free(cp); + } +} + +/* + * In each case below we allow for job==NULL + */ +void +meta_job_start(Job *job, GNode *gn) +{ + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + pbm->mfp = meta_create(pbm, gn); +#ifdef USE_FILEMON_ONCE + /* compat mode we open the filemon dev once per command */ + if (job == NULL) + return; +#endif +#ifdef USE_FILEMON + if (pbm->mfp != NULL && useFilemon) { + filemon_open(pbm); + } else { + pbm->mon_fd = pbm->filemon_fd = -1; + } +#endif +} + +/* + * The child calls this before doing anything. + * It does not disturb our state. + */ +void +meta_job_child(Job *job) +{ +#ifdef USE_FILEMON + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (pbm->mfp != NULL) { + close(fileno(pbm->mfp)); + if (useFilemon) { + pid_t pid; + + pid = getpid(); + if (ioctl(pbm->filemon_fd, FILEMON_SET_PID, &pid) < 0) { + err(1, "Could not set filemon pid!"); + } + } + } +#endif +} + +void +meta_job_error(Job *job, GNode *gn, int flags, int status) +{ + char cwd[MAXPATHLEN]; + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + if (!gn) + gn = job->node; + } else { + pbm = &Mybm; + } + if (pbm->mfp != NULL) { + fprintf(pbm->mfp, "\n*** Error code %d%s\n", + status, + (flags & JOB_IGNERR) ? + "(ignored)" : ""); + } + if (gn) { + Var_Set(".ERROR_TARGET", gn->path ? gn->path : gn->name, VAR_GLOBAL, 0); + } + getcwd(cwd, sizeof(cwd)); + Var_Set(".ERROR_CWD", cwd, VAR_GLOBAL, 0); + if (pbm->meta_fname[0]) { + Var_Set(".ERROR_META_FILE", pbm->meta_fname, VAR_GLOBAL, 0); + } + meta_job_finish(job); +} + +void +meta_job_output(Job *job, char *cp, const char *nl) +{ + BuildMon *pbm; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (pbm->mfp != NULL) { + if (metaVerbose) { + static char *meta_prefix = NULL; + static int meta_prefix_len; + + if (!meta_prefix) { + char *cp2; + + meta_prefix = Var_Subst(NULL, "${" MAKE_META_PREFIX "}", + VAR_GLOBAL, VARF_WANTRES); + if ((cp2 = strchr(meta_prefix, '$'))) + meta_prefix_len = cp2 - meta_prefix; + else + meta_prefix_len = strlen(meta_prefix); + } + if (strncmp(cp, meta_prefix, meta_prefix_len) == 0) { + cp = strchr(cp+1, '\n'); + if (!cp++) + return; + } + } + fprintf(pbm->mfp, "%s%s", cp, nl); + } +} + +int +meta_cmd_finish(void *pbmp) +{ + int error = 0; + BuildMon *pbm = pbmp; +#ifdef USE_FILEMON + int x; +#endif + + if (!pbm) + pbm = &Mybm; + +#ifdef USE_FILEMON + if (pbm->filemon_fd >= 0) { + if (close(pbm->filemon_fd) < 0) + error = errno; + x = filemon_read(pbm->mfp, pbm->mon_fd); + if (error == 0 && x != 0) + error = x; + pbm->filemon_fd = pbm->mon_fd = -1; + } else +#endif + fprintf(pbm->mfp, "\n"); /* ensure end with newline */ + return error; +} + +int +meta_job_finish(Job *job) +{ + BuildMon *pbm; + int error = 0; + int x; + + if (job != NULL) { + pbm = &job->bm; + } else { + pbm = &Mybm; + } + if (pbm->mfp != NULL) { + error = meta_cmd_finish(pbm); + x = fclose(pbm->mfp); + if (error == 0 && x != 0) + error = errno; + pbm->mfp = NULL; + pbm->meta_fname[0] = '\0'; + } + return error; +} + +void +meta_finish(void) +{ + Lst_Destroy(metaBailiwick, NULL); + free(metaBailiwickStr); + Lst_Destroy(metaIgnorePaths, NULL); + free(metaIgnorePathsStr); +} + +/* + * Fetch a full line from fp - growing bufp if needed + * Return length in bufp. + */ +static int +fgetLine(char **bufp, size_t *szp, int o, FILE *fp) +{ + char *buf = *bufp; + size_t bufsz = *szp; + struct stat fs; + int x; + + if (fgets(&buf[o], bufsz - o, fp) != NULL) { + check_newline: + x = o + strlen(&buf[o]); + if (buf[x - 1] == '\n') + return x; + /* + * We need to grow the buffer. + * The meta file can give us a clue. + */ + if (fstat(fileno(fp), &fs) == 0) { + size_t newsz; + char *p; + + newsz = ROUNDUP((fs.st_size / 2), BUFSIZ); + if (newsz <= bufsz) + newsz = ROUNDUP(fs.st_size, BUFSIZ); + if (newsz <= bufsz) + return x; /* truncated */ + if (DEBUG(META)) + fprintf(debug_file, "growing buffer %zu -> %zu\n", + bufsz, newsz); + p = bmake_realloc(buf, newsz); + if (p) { + *bufp = buf = p; + *szp = bufsz = newsz; + /* fetch the rest */ + if (!fgets(&buf[x], bufsz - x, fp)) + return x; /* truncated! */ + goto check_newline; + } + } + } + return 0; +} + +/* Lst_ForEach wants 1 to stop search */ +static int +prefix_match(void *p, void *q) +{ + const char *prefix = p; + const char *path = q; + size_t n = strlen(prefix); + + return (0 == strncmp(path, prefix, n)); +} + +/* + * looking for exact or prefix/ match to + * Lst_Find wants 0 to stop search + */ +static int +path_match(const void *p, const void *q) +{ + const char *prefix = q; + const char *path = p; + size_t n = strlen(prefix); + int rc; + + if ((rc = strncmp(path, prefix, n)) == 0) { + switch (path[n]) { + case '\0': + case '/': + break; + default: + rc = 1; + break; + } + } + return rc; +} + +/* Lst_Find wants 0 to stop search */ +static int +string_match(const void *p, const void *q) +{ + const char *p1 = p; + const char *p2 = q; + + return strcmp(p1, p2); +} + + +static int +meta_ignore(GNode *gn, const char *p) +{ + char fname[MAXPATHLEN]; + + if (p == NULL) + return TRUE; + + if (*p == '/') { + cached_realpath(p, fname); /* clean it up */ + if (Lst_ForEach(metaIgnorePaths, prefix_match, fname)) { +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: ignoring path: %s\n", + p); +#endif + return TRUE; + } + } + + if (metaIgnorePatterns) { + char *pm; + + Var_Set(".p.", p, gn, 0); + pm = Var_Subst(NULL, + "${" MAKE_META_IGNORE_PATTERNS ":@m@${.p.:M$m}@}", + gn, VARF_WANTRES); + if (*pm) { +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: ignoring pattern: %s\n", + p); +#endif + free(pm); + return TRUE; + } + free(pm); + } + + if (metaIgnoreFilter) { + char *fm; + + /* skip if filter result is empty */ + snprintf(fname, sizeof(fname), + "${%s:L:${%s:ts:}}", + p, MAKE_META_IGNORE_FILTER); + fm = Var_Subst(NULL, fname, gn, VARF_WANTRES); + if (*fm == '\0') { +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: ignoring filtered: %s\n", + p); +#endif + free(fm); + return TRUE; + } + free(fm); + } + return FALSE; +} + +/* + * When running with 'meta' functionality, a target can be out-of-date + * if any of the references in its meta data file is more recent. + * We have to track the latestdir on a per-process basis. + */ +#define LCWD_VNAME_FMT ".meta.%d.lcwd" +#define LDIR_VNAME_FMT ".meta.%d.ldir" + +/* + * It is possible that a .meta file is corrupted, + * if we detect this we want to reproduce it. + * Setting oodate TRUE will have that effect. + */ +#define CHECK_VALID_META(p) if (!(p && *p)) { \ + warnx("%s: %d: malformed", fname, lineno); \ + oodate = TRUE; \ + continue; \ + } + +#define DEQUOTE(p) if (*p == '\'') { \ + char *ep; \ + p++; \ + if ((ep = strchr(p, '\''))) \ + *ep = '\0'; \ + } + +Boolean +meta_oodate(GNode *gn, Boolean oodate) +{ + static char *tmpdir = NULL; + static char cwd[MAXPATHLEN]; + char lcwd_vname[64]; + char ldir_vname[64]; + char lcwd[MAXPATHLEN]; + char latestdir[MAXPATHLEN]; + char fname[MAXPATHLEN]; + char fname1[MAXPATHLEN]; + char fname2[MAXPATHLEN]; + char fname3[MAXPATHLEN]; + const char *dname; + const char *tname; + char *p; + char *cp; + char *link_src; + char *move_target; + static size_t cwdlen = 0; + static size_t tmplen = 0; + FILE *fp; + Boolean needOODATE = FALSE; + Lst missingFiles; + char *pa[4]; /* >= possible uses */ + int i; + int have_filemon = FALSE; + + if (oodate) + return oodate; /* we're done */ + + i = 0; + + dname = Var_Value(".OBJDIR", gn, &pa[i++]); + tname = Var_Value(TARGET, gn, &pa[i++]); + + /* if this succeeds fname3 is realpath of dname */ + if (!meta_needed(gn, dname, tname, fname3, FALSE)) + goto oodate_out; + dname = fname3; + + missingFiles = Lst_Init(FALSE); + + /* + * We need to check if the target is out-of-date. This includes + * checking if the expanded command has changed. This in turn + * requires that all variables are set in the same way that they + * would be if the target needs to be re-built. + */ + Make_DoAllVar(gn); + + meta_name(gn, fname, sizeof(fname), dname, tname, dname); + +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: %s\n", fname); +#endif + + if ((fp = fopen(fname, "r")) != NULL) { + static char *buf = NULL; + static size_t bufsz; + int lineno = 0; + int lastpid = 0; + int pid; + int x; + LstNode ln; + struct stat fs; + + if (!buf) { + bufsz = 8 * BUFSIZ; + buf = bmake_malloc(bufsz); + } + + if (!cwdlen) { + if (getcwd(cwd, sizeof(cwd)) == NULL) + err(1, "Could not get current working directory"); + cwdlen = strlen(cwd); + } + strlcpy(lcwd, cwd, sizeof(lcwd)); + strlcpy(latestdir, cwd, sizeof(latestdir)); + + if (!tmpdir) { + tmpdir = getTmpdir(); + tmplen = strlen(tmpdir); + } + + /* we want to track all the .meta we read */ + Var_Append(".MAKE.META.FILES", fname, VAR_GLOBAL); + + ln = Lst_First(gn->commands); + while (!oodate && (x = fgetLine(&buf, &bufsz, 0, fp)) > 0) { + lineno++; + if (buf[x - 1] == '\n') + buf[x - 1] = '\0'; + else { + warnx("%s: %d: line truncated at %u", fname, lineno, x); + oodate = TRUE; + break; + } + link_src = NULL; + move_target = NULL; + /* Find the start of the build monitor section. */ + if (!have_filemon) { + if (strncmp(buf, "-- filemon", 10) == 0) { + have_filemon = TRUE; + continue; + } + if (strncmp(buf, "# buildmon", 10) == 0) { + have_filemon = TRUE; + continue; + } + } + + /* Delimit the record type. */ + p = buf; +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: %s\n", fname, lineno, buf); +#endif + strsep(&p, " "); + if (have_filemon) { + /* + * We are in the 'filemon' output section. + * Each record from filemon follows the general form: + * + * + * + * Where: + * is a single letter, denoting the syscall. + * is the process that made the syscall. + * is the arguments (of interest). + */ + switch(buf[0]) { + case '#': /* comment */ + case 'V': /* version */ + break; + default: + /* + * We need to track pathnames per-process. + * + * Each process run by make, starts off in the 'CWD' + * recorded in the .meta file, if it chdirs ('C') + * elsewhere we need to track that - but only for + * that process. If it forks ('F'), we initialize + * the child to have the same cwd as its parent. + * + * We also need to track the 'latestdir' of + * interest. This is usually the same as cwd, but + * not if a process is reading directories. + * + * Each time we spot a different process ('pid') + * we save the current value of 'latestdir' in a + * variable qualified by 'lastpid', and + * re-initialize 'latestdir' to any pre-saved + * value for the current 'pid' and 'CWD' if none. + */ + CHECK_VALID_META(p); + pid = atoi(p); + if (pid > 0 && pid != lastpid) { + char *ldir; + char *tp; + + if (lastpid > 0) { + /* We need to remember these. */ + Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); + Var_Set(ldir_vname, latestdir, VAR_GLOBAL, 0); + } + snprintf(lcwd_vname, sizeof(lcwd_vname), LCWD_VNAME_FMT, pid); + snprintf(ldir_vname, sizeof(ldir_vname), LDIR_VNAME_FMT, pid); + lastpid = pid; + ldir = Var_Value(ldir_vname, VAR_GLOBAL, &tp); + if (ldir) { + strlcpy(latestdir, ldir, sizeof(latestdir)); + free(tp); + } + ldir = Var_Value(lcwd_vname, VAR_GLOBAL, &tp); + if (ldir) { + strlcpy(lcwd, ldir, sizeof(lcwd)); + free(tp); + } + } + /* Skip past the pid. */ + if (strsep(&p, " ") == NULL) + continue; +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: %d: %c: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + pid, buf[0], cwd, lcwd, latestdir); +#endif + break; + } + + CHECK_VALID_META(p); + + /* Process according to record type. */ + switch (buf[0]) { + case 'X': /* eXit */ + Var_Delete(lcwd_vname, VAR_GLOBAL); + Var_Delete(ldir_vname, VAR_GLOBAL); + lastpid = 0; /* no need to save ldir_vname */ + break; + + case 'F': /* [v]Fork */ + { + char cldir[64]; + int child; + + child = atoi(p); + if (child > 0) { + snprintf(cldir, sizeof(cldir), LCWD_VNAME_FMT, child); + Var_Set(cldir, lcwd, VAR_GLOBAL, 0); + snprintf(cldir, sizeof(cldir), LDIR_VNAME_FMT, child); + Var_Set(cldir, latestdir, VAR_GLOBAL, 0); +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: %d: cwd=%s lcwd=%s ldir=%s\n", + fname, lineno, + child, cwd, lcwd, latestdir); +#endif + } + } + break; + + case 'C': /* Chdir */ + /* Update lcwd and latest directory. */ + strlcpy(latestdir, p, sizeof(latestdir)); + strlcpy(lcwd, p, sizeof(lcwd)); + Var_Set(lcwd_vname, lcwd, VAR_GLOBAL, 0); + Var_Set(ldir_vname, lcwd, VAR_GLOBAL, 0); +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: cwd=%s ldir=%s\n", fname, lineno, cwd, lcwd); +#endif + break; + + case 'M': /* renaMe */ + /* + * For 'M'oves we want to check + * the src as for 'R'ead + * and the target as for 'W'rite. + */ + cp = p; /* save this for a second */ + /* now get target */ + if (strsep(&p, " ") == NULL) + continue; + CHECK_VALID_META(p); + move_target = p; + p = cp; + /* 'L' and 'M' put single quotes around the args */ + DEQUOTE(p); + DEQUOTE(move_target); + /* FALLTHROUGH */ + case 'D': /* unlink */ + if (*p == '/' && !Lst_IsEmpty(missingFiles)) { + /* remove any missingFiles entries that match p */ + if ((ln = Lst_Find(missingFiles, p, + path_match)) != NULL) { + LstNode nln; + char *tp; + + do { + nln = Lst_FindFrom(missingFiles, Lst_Succ(ln), + p, path_match); + tp = Lst_Datum(ln); + Lst_Remove(missingFiles, ln); + free(tp); + } while ((ln = nln) != NULL); + } + } + if (buf[0] == 'M') { + /* the target of the mv is a file 'W'ritten */ +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: M %s -> %s\n", + p, move_target); +#endif + p = move_target; + goto check_write; + } + break; + case 'L': /* Link */ + /* + * For 'L'inks check + * the src as for 'R'ead + * and the target as for 'W'rite. + */ + link_src = p; + /* now get target */ + if (strsep(&p, " ") == NULL) + continue; + CHECK_VALID_META(p); + /* 'L' and 'M' put single quotes around the args */ + DEQUOTE(p); + DEQUOTE(link_src); +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: L %s -> %s\n", + link_src, p); +#endif + /* FALLTHROUGH */ + case 'W': /* Write */ + check_write: + /* + * If a file we generated within our bailiwick + * but outside of .OBJDIR is missing, + * we need to do it again. + */ + /* ignore non-absolute paths */ + if (*p != '/') + break; + + if (Lst_IsEmpty(metaBailiwick)) + break; + + /* ignore cwd - normal dependencies handle those */ + if (strncmp(p, cwd, cwdlen) == 0) + break; + + if (!Lst_ForEach(metaBailiwick, prefix_match, p)) + break; + + /* tmpdir might be within */ + if (tmplen > 0 && strncmp(p, tmpdir, tmplen) == 0) + break; + + /* ignore anything containing the string "tmp" */ + if ((strstr("tmp", p))) + break; + + if ((link_src != NULL && cached_lstat(p, &fs) < 0) || + (link_src == NULL && cached_stat(p, &fs) < 0)) { + if (!meta_ignore(gn, p)) { + if (Lst_Find(missingFiles, p, string_match) == NULL) + Lst_AtEnd(missingFiles, bmake_strdup(p)); + } + } + break; + check_link_src: + p = link_src; + link_src = NULL; +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "meta_oodate: L src %s\n", p); +#endif + /* FALLTHROUGH */ + case 'R': /* Read */ + case 'E': /* Exec */ + /* + * Check for runtime files that can't + * be part of the dependencies because + * they are _expected_ to change. + */ + if (meta_ignore(gn, p)) + break; + + /* + * The rest of the record is the file name. + * Check if it's not an absolute path. + */ + { + char *sdirs[4]; + char **sdp; + int sdx = 0; + int found = 0; + + if (*p == '/') { + sdirs[sdx++] = p; /* done */ + } else { + if (strcmp(".", p) == 0) + continue; /* no point */ + + /* Check vs latestdir */ + snprintf(fname1, sizeof(fname1), "%s/%s", latestdir, p); + sdirs[sdx++] = fname1; + + if (strcmp(latestdir, lcwd) != 0) { + /* Check vs lcwd */ + snprintf(fname2, sizeof(fname2), "%s/%s", lcwd, p); + sdirs[sdx++] = fname2; + } + if (strcmp(lcwd, cwd) != 0) { + /* Check vs cwd */ + snprintf(fname3, sizeof(fname3), "%s/%s", cwd, p); + sdirs[sdx++] = fname3; + } + } + sdirs[sdx++] = NULL; + + for (sdp = sdirs; *sdp && !found; sdp++) { +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: looking for: %s\n", fname, lineno, *sdp); +#endif + if (cached_stat(*sdp, &fs) == 0) { + found = 1; + p = *sdp; + } + } + if (found) { +#ifdef DEBUG_META_MODE + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: found: %s\n", fname, lineno, p); +#endif + if (!S_ISDIR(fs.st_mode) && + fs.st_mtime > gn->mtime) { + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: file '%s' is newer than the target...\n", fname, lineno, p); + oodate = TRUE; + } else if (S_ISDIR(fs.st_mode)) { + /* Update the latest directory. */ + cached_realpath(p, latestdir); + } + } else if (errno == ENOENT && *p == '/' && + strncmp(p, cwd, cwdlen) != 0) { + /* + * A referenced file outside of CWD is missing. + * We cannot catch every eventuality here... + */ + if (Lst_Find(missingFiles, p, string_match) == NULL) + Lst_AtEnd(missingFiles, bmake_strdup(p)); + } + } + if (buf[0] == 'E') { + /* previous latestdir is no longer relevant */ + strlcpy(latestdir, lcwd, sizeof(latestdir)); + } + break; + default: + break; + } + if (!oodate && buf[0] == 'L' && link_src != NULL) + goto check_link_src; + } else if (strcmp(buf, "CMD") == 0) { + /* + * Compare the current command with the one in the + * meta data file. + */ + if (ln == NULL) { + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: there were more build commands in the meta data file than there are now...\n", fname, lineno); + oodate = TRUE; + } else { + char *cmd = (char *)Lst_Datum(ln); + Boolean hasOODATE = FALSE; + + if (strstr(cmd, "$?")) + hasOODATE = TRUE; + else if ((cp = strstr(cmd, ".OODATE"))) { + /* check for $[{(].OODATE[:)}] */ + if (cp > cmd + 2 && cp[-2] == '$') + hasOODATE = TRUE; + } + if (hasOODATE) { + needOODATE = TRUE; + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: cannot compare command using .OODATE\n", fname, lineno); + } + cmd = Var_Subst(NULL, cmd, gn, VARF_WANTRES|VARF_UNDEFERR); + + if ((cp = strchr(cmd, '\n'))) { + int n; + + /* + * This command contains newlines, we need to + * fetch more from the .meta file before we + * attempt a comparison. + */ + /* first put the newline back at buf[x - 1] */ + buf[x - 1] = '\n'; + do { + /* now fetch the next line */ + if ((n = fgetLine(&buf, &bufsz, x, fp)) <= 0) + break; + x = n; + lineno++; + if (buf[x - 1] != '\n') { + warnx("%s: %d: line truncated at %u", fname, lineno, x); + break; + } + cp = strchr(++cp, '\n'); + } while (cp); + if (buf[x - 1] == '\n') + buf[x - 1] = '\0'; + } + if (!hasOODATE && + !(gn->type & OP_NOMETA_CMP) && + strcmp(p, cmd) != 0) { + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: a build command has changed\n%s\nvs\n%s\n", fname, lineno, p, cmd); + if (!metaIgnoreCMDs) + oodate = TRUE; + } + free(cmd); + ln = Lst_Succ(ln); + } + } else if (strcmp(buf, "CWD") == 0) { + /* + * Check if there are extra commands now + * that weren't in the meta data file. + */ + if (!oodate && ln != NULL) { + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: there are extra build commands now that weren't in the meta data file\n", fname, lineno); + oodate = TRUE; + } + if (strcmp(p, cwd) != 0) { + if (DEBUG(META)) + fprintf(debug_file, "%s: %d: the current working directory has changed from '%s' to '%s'\n", fname, lineno, p, curdir); + oodate = TRUE; + } + } + } + + fclose(fp); + if (!Lst_IsEmpty(missingFiles)) { + if (DEBUG(META)) + fprintf(debug_file, "%s: missing files: %s...\n", + fname, (char *)Lst_Datum(Lst_First(missingFiles))); + oodate = TRUE; + } + if (!oodate && !have_filemon && filemonMissing) { + if (DEBUG(META)) + fprintf(debug_file, "%s: missing filemon data\n", fname); + oodate = TRUE; + } + } else { + if (writeMeta && metaMissing) { + cp = NULL; + + /* if target is in .CURDIR we do not need a meta file */ + if (gn->path && (cp = strrchr(gn->path, '/')) && cp > gn->path) { + if (strncmp(curdir, gn->path, (cp - gn->path)) != 0) { + cp = NULL; /* not in .CURDIR */ + } + } + if (!cp) { + if (DEBUG(META)) + fprintf(debug_file, "%s: required but missing\n", fname); + oodate = TRUE; + needOODATE = TRUE; /* assume the worst */ + } + } + } + + Lst_Destroy(missingFiles, (FreeProc *)free); + + if (oodate && needOODATE) { + /* + * Target uses .OODATE which is empty; or we wouldn't be here. + * We have decided it is oodate, so .OODATE needs to be set. + * All we can sanely do is set it to .ALLSRC. + */ + Var_Delete(OODATE, gn); + Var_Set(OODATE, Var_Value(ALLSRC, gn, &cp), gn, 0); + free(cp); + } + + oodate_out: + for (i--; i >= 0; i--) { + free(pa[i]); + } + return oodate; +} + +/* support for compat mode */ + +static int childPipe[2]; + +void +meta_compat_start(void) +{ +#ifdef USE_FILEMON_ONCE + /* + * We need to re-open filemon for each cmd. + */ + BuildMon *pbm = &Mybm; + + if (pbm->mfp != NULL && useFilemon) { + filemon_open(pbm); + } else { + pbm->mon_fd = pbm->filemon_fd = -1; + } +#endif + if (pipe(childPipe) < 0) + Punt("Cannot create pipe: %s", strerror(errno)); + /* Set close-on-exec flag for both */ + (void)fcntl(childPipe[0], F_SETFD, FD_CLOEXEC); + (void)fcntl(childPipe[1], F_SETFD, FD_CLOEXEC); +} + +void +meta_compat_child(void) +{ + meta_job_child(NULL); + if (dup2(childPipe[1], 1) < 0 || + dup2(1, 2) < 0) { + execError("dup2", "pipe"); + _exit(1); + } +} + +void +meta_compat_parent(void) +{ + FILE *fp; + char buf[BUFSIZ]; + + close(childPipe[1]); /* child side */ + fp = fdopen(childPipe[0], "r"); + while (fgets(buf, sizeof(buf), fp)) { + meta_job_output(NULL, buf, ""); + printf("%s", buf); + fflush(stdout); + } + fclose(fp); +} + +#endif /* USE_META */ diff --git a/usr.bin/make/meta.h b/usr.bin/make/meta.h new file mode 100644 index 0000000..8f1018e --- /dev/null +++ b/usr.bin/make/meta.h @@ -0,0 +1,56 @@ +/* $NetBSD: meta.h,v 1.5 2016/05/12 20:28:34 sjg Exp $ */ + +/* + * Things needed for 'meta' mode. + */ +/* + * Copyright (c) 2009-2010, Juniper Networks, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +typedef struct BuildMon { + char meta_fname[MAXPATHLEN]; + int filemon_fd; + int mon_fd; + FILE *mfp; +} BuildMon; + +extern Boolean useMeta; + +struct Job; /* not defined yet */ +void meta_init(void); +void meta_finish(void); +void meta_mode_init(const char *); +void meta_job_start(struct Job *, GNode *); +void meta_job_child(struct Job *); +void meta_job_error(struct Job *, GNode *, int, int); +void meta_job_output(struct Job *, char *, const char *); +int meta_cmd_finish(void *); +int meta_job_finish(struct Job *); +Boolean meta_oodate(GNode *, Boolean); +void meta_compat_start(void); +void meta_compat_child(void); +void meta_compat_parent(void); diff --git a/usr.bin/make/metachar.c b/usr.bin/make/metachar.c new file mode 100644 index 0000000..4960338 --- /dev/null +++ b/usr.bin/make/metachar.c @@ -0,0 +1,88 @@ +/* $NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $ */ + +/*- + * Copyright (c) 2015 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#if defined(MAKE_NATIVE) || defined(HAVE_NBTOOL_CONFIG_H) +#include +#endif + +#if defined(__RCSID) && !defined(lint) +__RCSID("$NetBSD: metachar.c,v 1.5 2015/06/19 08:03:35 mlelstv Exp $"); +#endif + +#include "metachar.h" +/* + * The following array is used to make a fast determination of which + * characters are interpreted specially by the shell. If a command + * contains any of these characters, it is executed by the shell, not + * directly by us. + * + * perhaps move it to ctype? + */ + +unsigned char _metachar[128] = { +// nul soh stx etx eot enq ack bel + 1, 0, 0, 0, 0, 0, 0, 0, +// bs ht nl vt np cr so si + 0, 0, 1, 0, 0, 0, 0, 0, +// dle dc1 dc2 dc3 dc4 nak syn etb + 0, 0, 0, 0, 0, 0, 0, 0, +// can em sub esc fs gs rs us + 0, 0, 0, 0, 0, 0, 0, 0, +// sp ! " # $ % & ' + 0, 1, 1, 1, 1, 0, 1, 1, +// ( ) * + , - . / + 1, 1, 1, 0, 0, 0, 0, 0, +// 0 1 2 3 4 5 6 7 + 0, 0, 0, 0, 0, 0, 0, 0, +// 8 9 : ; < = > ? + 0, 0, 0, 1, 1, 0, 1, 1, +// @ A B C D E F G + 0, 0, 0, 0, 0, 0, 0, 0, +// H I J K L M N O + 0, 0, 0, 0, 0, 0, 0, 0, +// P Q R S T U V W + 0, 0, 0, 0, 0, 0, 0, 0, +// X Y Z [ \ ] ^ _ + 0, 0, 0, 1, 1, 1, 1, 0, +// ` a b c d e f g + 1, 0, 0, 0, 0, 0, 0, 0, +// h i j k l m n o + 0, 0, 0, 0, 0, 0, 0, 0, +// p q r s t u v w + 0, 0, 0, 0, 0, 0, 0, 0, +// x y z { | } ~ del + 0, 0, 0, 1, 1, 1, 1, 0, +}; + diff --git a/usr.bin/make/metachar.h b/usr.bin/make/metachar.h new file mode 100644 index 0000000..db88d67 --- /dev/null +++ b/usr.bin/make/metachar.h @@ -0,0 +1,61 @@ +/* $NetBSD: metachar.h,v 1.4 2015/06/21 20:26:02 christos Exp $ */ + +/*- + * Copyright (c) 2015 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _METACHAR_H +#define _METACHAR_H + +#include + +extern unsigned char _metachar[]; + +#define ismeta(c) _metachar[(c) & 0x7f] + +static inline int +hasmeta(const char *cmd) +{ + while (!ismeta(*cmd)) + cmd++; + + return *cmd != '\0'; +} + +static inline int +needshell(const char *cmd, int white) +{ + while (!ismeta(*cmd) && *cmd != ':' && *cmd != '=') { + if (white && isspace((unsigned char)*cmd)) + break; + cmd++; + } + + return *cmd != '\0'; +} + +#endif /* _METACHAR_H */ diff --git a/usr.bin/make/nonints.h b/usr.bin/make/nonints.h new file mode 100644 index 0000000..2fe2330 --- /dev/null +++ b/usr.bin/make/nonints.h @@ -0,0 +1,198 @@ +/* $NetBSD: nonints.h,v 1.74 2016/09/05 00:40:29 sevan Exp $ */ + +/*- + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 + */ + +/*- + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)nonints.h 8.3 (Berkeley) 3/19/94 + */ + +/* arch.c */ +ReturnStatus Arch_ParseArchive(char **, Lst, GNode *); +void Arch_Touch(GNode *); +void Arch_TouchLib(GNode *); +time_t Arch_MTime(GNode *); +time_t Arch_MemMTime(GNode *); +void Arch_FindLib(GNode *, Lst); +Boolean Arch_LibOODate(GNode *); +void Arch_Init(void); +void Arch_End(void); +int Arch_IsLib(GNode *); + +/* compat.c */ +int CompatRunCommand(void *, void *); +void Compat_Run(Lst); +int Compat_Make(void *, void *); + +/* cond.c */ +struct If; +int Cond_EvalExpression(const struct If *, char *, Boolean *, int, Boolean); +int Cond_Eval(char *); +void Cond_restore_depth(unsigned int); +unsigned int Cond_save_depth(void); + +/* for.c */ +int For_Eval(char *); +int For_Accum(char *); +void For_Run(int); + +/* job.c */ +void JobReapChild(pid_t, int, Boolean); + +/* main.c */ +void Main_ParseArgLine(const char *); +void MakeMode(const char *); +char *Cmd_Exec(const char *, const char **); +void Error(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2); +void Fatal(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; +void Punt(const char *, ...) MAKE_ATTR_PRINTFLIKE(1, 2) MAKE_ATTR_DEAD; +void DieHorribly(void) MAKE_ATTR_DEAD; +int PrintAddr(void *, void *); +void Finish(int) MAKE_ATTR_DEAD; +int eunlink(const char *); +void execError(const char *, const char *); +char *getTmpdir(void); +Boolean s2Boolean(const char *, Boolean); +Boolean getBoolean(const char *, Boolean); +char *cached_realpath(const char *, char *); + +/* parse.c */ +void Parse_Error(int, const char *, ...) MAKE_ATTR_PRINTFLIKE(2, 3); +Boolean Parse_AnyExport(void); +Boolean Parse_IsVar(char *); +void Parse_DoVar(char *, GNode *); +void Parse_AddIncludeDir(char *); +void Parse_File(const char *, int); +void Parse_Init(void); +void Parse_End(void); +void Parse_SetInput(const char *, int, int, char *(*)(void *, size_t *), void *); +Lst Parse_MainName(void); + +/* str.c */ +char *str_concat(const char *, const char *, int); +char **brk_string(const char *, int *, Boolean, char **); +char *Str_FindSubstring(const char *, const char *); +int Str_Match(const char *, const char *); +char *Str_SYSVMatch(const char *, const char *, int *len); +void Str_SYSVSubst(Buffer *, char *, char *, int); + +/* suff.c */ +void Suff_ClearSuffixes(void); +Boolean Suff_IsTransform(char *); +GNode *Suff_AddTransform(char *); +int Suff_EndTransform(void *, void *); +void Suff_AddSuffix(char *, GNode **); +Lst Suff_GetPath(char *); +void Suff_DoPaths(void); +void Suff_AddInclude(char *); +void Suff_AddLib(char *); +void Suff_FindDeps(GNode *); +Lst Suff_FindPath(GNode *); +void Suff_SetNull(char *); +void Suff_Init(void); +void Suff_End(void); +void Suff_PrintAll(void); + +/* targ.c */ +void Targ_Init(void); +void Targ_End(void); +Lst Targ_List(void); +GNode *Targ_NewGN(const char *); +GNode *Targ_FindNode(const char *, int); +Lst Targ_FindList(Lst, int); +Boolean Targ_Ignore(GNode *); +Boolean Targ_Silent(GNode *); +Boolean Targ_Precious(GNode *); +void Targ_SetMain(GNode *); +int Targ_PrintCmd(void *, void *); +int Targ_PrintNode(void *, void *); +char *Targ_FmtTime(time_t); +void Targ_PrintType(int); +void Targ_PrintGraph(int); +void Targ_Propagate(void); +void Targ_Propagate_Wait(void); + +/* var.c */ +void Var_Delete(const char *, GNode *); +void Var_Set(const char *, const char *, GNode *, int); +void Var_Append(const char *, const char *, GNode *); +Boolean Var_Exists(const char *, GNode *); +char *Var_Value(const char *, GNode *, char **); +char *Var_Parse(const char *, GNode *, int, int *, void **); +char *Var_Subst(const char *, const char *, GNode *, int); +char *Var_GetTail(const char *); +char *Var_GetHead(const char *); +void Var_Init(void); +void Var_End(void); +void Var_Dump(GNode *); +void Var_ExportVars(void); +void Var_Export(char *, int); +void Var_UnExport(char *); + +/* util.c */ +void (*bmake_signal(int, void (*)(int)))(int); diff --git a/usr.bin/make/parse.c b/usr.bin/make/parse.c new file mode 100644 index 0000000..d7cecec --- /dev/null +++ b/usr.bin/make/parse.c @@ -0,0 +1,3357 @@ +/* $NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)parse.c 8.3 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: parse.c,v 1.231 2018/12/22 00:36:32 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * parse.c -- + * Functions to parse a makefile. + * + * One function, Parse_Init, must be called before any functions + * in this module are used. After that, the function Parse_File is the + * main entry point and controls most of the other functions in this + * module. + * + * Most important structures are kept in Lsts. Directories for + * the .include "..." function are kept in the 'parseIncPath' Lst, while + * those for the .include <...> are kept in the 'sysIncPath' Lst. The + * targets currently being defined are kept in the 'targets' Lst. + * + * The variables 'fname' and 'lineno' are used to track the name + * of the current file and the line number in that file so that error + * messages can be more meaningful. + * + * Interface: + * Parse_Init Initialization function which must be + * called before anything else in this module + * is used. + * + * Parse_End Cleanup the module + * + * Parse_File Function used to parse a makefile. It must + * be given the name of the file, which should + * already have been opened, and a function + * to call to read a character from the file. + * + * Parse_IsVar Returns TRUE if the given line is a + * variable assignment. Used by MainParseArgs + * to determine if an argument is a target + * or a variable assignment. Used internally + * for pretty much the same thing... + * + * Parse_Error Function called when an error occurs in + * parsing. Used by the variable and + * conditional modules. + * Parse_MainName Returns a Lst of the main target to create. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MAP_FILE +#define MAP_FILE 0 +#endif +#ifndef MAP_COPY +#define MAP_COPY MAP_PRIVATE +#endif + +#include "make.h" +#include "hash.h" +#include "dir.h" +#include "job.h" +#include "buf.h" +#include "pathnames.h" + +//////////////////////////////////////////////////////////// +// types and constants + +/* + * Structure for a file being read ("included file") + */ +typedef struct IFile { + char *fname; /* name of file */ + int lineno; /* current line number in file */ + int first_lineno; /* line number of start of text */ + int cond_depth; /* 'if' nesting when file opened */ + Boolean depending; /* state of doing_depend on EOF */ + char *P_str; /* point to base of string buffer */ + char *P_ptr; /* point to next char of string buffer */ + char *P_end; /* point to the end of string buffer */ + char *(*nextbuf)(void *, size_t *); /* Function to get more data */ + void *nextbuf_arg; /* Opaque arg for nextbuf() */ + struct loadedfile *lf; /* loadedfile object, if any */ +} IFile; + + +/* + * These values are returned by ParseEOF to tell Parse_File whether to + * CONTINUE parsing, i.e. it had only reached the end of an include file, + * or if it's DONE. + */ +#define CONTINUE 1 +#define DONE 0 + +/* + * Tokens for target attributes + */ +typedef enum { + Begin, /* .BEGIN */ + Default, /* .DEFAULT */ + DeleteOnError, /* .DELETE_ON_ERROR */ + End, /* .END */ + dotError, /* .ERROR */ + Ignore, /* .IGNORE */ + Includes, /* .INCLUDES */ + Interrupt, /* .INTERRUPT */ + Libs, /* .LIBS */ + Meta, /* .META */ + MFlags, /* .MFLAGS or .MAKEFLAGS */ + Main, /* .MAIN and we don't have anything user-specified to + * make */ + NoExport, /* .NOEXPORT */ + NoMeta, /* .NOMETA */ + NoMetaCmp, /* .NOMETA_CMP */ + NoPath, /* .NOPATH */ + Not, /* Not special */ + NotParallel, /* .NOTPARALLEL */ + Null, /* .NULL */ + ExObjdir, /* .OBJDIR */ + Order, /* .ORDER */ + Parallel, /* .PARALLEL */ + ExPath, /* .PATH */ + Phony, /* .PHONY */ +#ifdef POSIX + Posix, /* .POSIX */ +#endif + Precious, /* .PRECIOUS */ + ExShell, /* .SHELL */ + Silent, /* .SILENT */ + SingleShell, /* .SINGLESHELL */ + Stale, /* .STALE */ + Suffixes, /* .SUFFIXES */ + Wait, /* .WAIT */ + Attribute /* Generic attribute */ +} ParseSpecial; + +/* + * Other tokens + */ +#define LPAREN '(' +#define RPAREN ')' + + +//////////////////////////////////////////////////////////// +// result data + +/* + * The main target to create. This is the first target on the first + * dependency line in the first makefile. + */ +static GNode *mainNode; + +//////////////////////////////////////////////////////////// +// eval state + +/* targets we're working on */ +static Lst targets; + +#ifdef CLEANUP +/* command lines for targets */ +static Lst targCmds; +#endif + +/* + * specType contains the SPECial TYPE of the current target. It is + * Not if the target is unspecial. If it *is* special, however, the children + * are linked as children of the parent but not vice versa. This variable is + * set in ParseDoDependency + */ +static ParseSpecial specType; + +/* + * Predecessor node for handling .ORDER. Initialized to NULL when .ORDER + * seen, then set to each successive source on the line. + */ +static GNode *predecessor; + +//////////////////////////////////////////////////////////// +// parser state + +/* true if currently in a dependency line or its commands */ +static Boolean inLine; + +/* number of fatal errors */ +static int fatals = 0; + +/* + * Variables for doing includes + */ + +/* current file being read */ +static IFile *curFile; + +/* stack of IFiles generated by .includes */ +static Lst includes; + +/* include paths (lists of directories) */ +Lst parseIncPath; /* dirs for "..." includes */ +Lst sysIncPath; /* dirs for <...> includes */ +Lst defIncPath; /* default for sysIncPath */ + +//////////////////////////////////////////////////////////// +// parser tables + +/* + * The parseKeywords table is searched using binary search when deciding + * if a target or source is special. The 'spec' field is the ParseSpecial + * type of the keyword ("Not" if the keyword isn't special as a target) while + * the 'op' field is the operator to apply to the list of targets if the + * keyword is used as a source ("0" if the keyword isn't special as a source) + */ +static const struct { + const char *name; /* Name of keyword */ + ParseSpecial spec; /* Type when used as a target */ + int op; /* Operator when used as a source */ +} parseKeywords[] = { +{ ".BEGIN", Begin, 0 }, +{ ".DEFAULT", Default, 0 }, +{ ".DELETE_ON_ERROR", DeleteOnError, 0 }, +{ ".END", End, 0 }, +{ ".ERROR", dotError, 0 }, +{ ".EXEC", Attribute, OP_EXEC }, +{ ".IGNORE", Ignore, OP_IGNORE }, +{ ".INCLUDES", Includes, 0 }, +{ ".INTERRUPT", Interrupt, 0 }, +{ ".INVISIBLE", Attribute, OP_INVISIBLE }, +{ ".JOIN", Attribute, OP_JOIN }, +{ ".LIBS", Libs, 0 }, +{ ".MADE", Attribute, OP_MADE }, +{ ".MAIN", Main, 0 }, +{ ".MAKE", Attribute, OP_MAKE }, +{ ".MAKEFLAGS", MFlags, 0 }, +{ ".META", Meta, OP_META }, +{ ".MFLAGS", MFlags, 0 }, +{ ".NOMETA", NoMeta, OP_NOMETA }, +{ ".NOMETA_CMP", NoMetaCmp, OP_NOMETA_CMP }, +{ ".NOPATH", NoPath, OP_NOPATH }, +{ ".NOTMAIN", Attribute, OP_NOTMAIN }, +{ ".NOTPARALLEL", NotParallel, 0 }, +{ ".NO_PARALLEL", NotParallel, 0 }, +{ ".NULL", Null, 0 }, +{ ".OBJDIR", ExObjdir, 0 }, +{ ".OPTIONAL", Attribute, OP_OPTIONAL }, +{ ".ORDER", Order, 0 }, +{ ".PARALLEL", Parallel, 0 }, +{ ".PATH", ExPath, 0 }, +{ ".PHONY", Phony, OP_PHONY }, +#ifdef POSIX +{ ".POSIX", Posix, 0 }, +#endif +{ ".PRECIOUS", Precious, OP_PRECIOUS }, +{ ".RECURSIVE", Attribute, OP_MAKE }, +{ ".SHELL", ExShell, 0 }, +{ ".SILENT", Silent, OP_SILENT }, +{ ".SINGLESHELL", SingleShell, 0 }, +{ ".STALE", Stale, 0 }, +{ ".SUFFIXES", Suffixes, 0 }, +{ ".USE", Attribute, OP_USE }, +{ ".USEBEFORE", Attribute, OP_USEBEFORE }, +{ ".WAIT", Wait, 0 }, +}; + +//////////////////////////////////////////////////////////// +// local functions + +static int ParseIsEscaped(const char *, const char *); +static void ParseErrorInternal(const char *, size_t, int, const char *, ...) + MAKE_ATTR_PRINTFLIKE(4,5); +static void ParseVErrorInternal(FILE *, const char *, size_t, int, const char *, va_list) + MAKE_ATTR_PRINTFLIKE(5, 0); +static int ParseFindKeyword(const char *); +static int ParseLinkSrc(void *, void *); +static int ParseDoOp(void *, void *); +static void ParseDoSrc(int, const char *); +static int ParseFindMain(void *, void *); +static int ParseAddDir(void *, void *); +static int ParseClearPath(void *, void *); +static void ParseDoDependency(char *); +static int ParseAddCmd(void *, void *); +static void ParseHasCommands(void *); +static void ParseDoInclude(char *); +static void ParseSetParseFile(const char *); +static void ParseSetIncludedFile(void); +#ifdef GMAKEEXPORT +static void ParseGmakeExport(char *); +#endif +static int ParseEOF(void); +static char *ParseReadLine(void); +static void ParseFinishLine(void); +static void ParseMark(GNode *); + +//////////////////////////////////////////////////////////// +// file loader + +struct loadedfile { + const char *path; /* name, for error reports */ + char *buf; /* contents buffer */ + size_t len; /* length of contents */ + size_t maplen; /* length of mmap area, or 0 */ + Boolean used; /* XXX: have we used the data yet */ +}; + +/* + * Constructor/destructor for loadedfile + */ +static struct loadedfile * +loadedfile_create(const char *path) +{ + struct loadedfile *lf; + + lf = bmake_malloc(sizeof(*lf)); + lf->path = (path == NULL ? "(stdin)" : path); + lf->buf = NULL; + lf->len = 0; + lf->maplen = 0; + lf->used = FALSE; + return lf; +} + +static void +loadedfile_destroy(struct loadedfile *lf) +{ + if (lf->buf != NULL) { + if (lf->maplen > 0) { + munmap(lf->buf, lf->maplen); + } else { + free(lf->buf); + } + } + free(lf); +} + +/* + * nextbuf() operation for loadedfile, as needed by the weird and twisted + * logic below. Once that's cleaned up, we can get rid of lf->used... + */ +static char * +loadedfile_nextbuf(void *x, size_t *len) +{ + struct loadedfile *lf = x; + + if (lf->used) { + return NULL; + } + lf->used = TRUE; + *len = lf->len; + return lf->buf; +} + +/* + * Try to get the size of a file. + */ +static ReturnStatus +load_getsize(int fd, size_t *ret) +{ + struct stat st; + + if (fstat(fd, &st) < 0) { + return FAILURE; + } + + if (!S_ISREG(st.st_mode)) { + return FAILURE; + } + + /* + * st_size is an off_t, which is 64 bits signed; *ret is + * size_t, which might be 32 bits unsigned or 64 bits + * unsigned. Rather than being elaborate, just punt on + * files that are more than 2^31 bytes. We should never + * see a makefile that size in practice... + * + * While we're at it reject negative sizes too, just in case. + */ + if (st.st_size < 0 || st.st_size > 0x7fffffff) { + return FAILURE; + } + + *ret = (size_t) st.st_size; + return SUCCESS; +} + +/* + * Read in a file. + * + * Until the path search logic can be moved under here instead of + * being in the caller in another source file, we need to have the fd + * passed in already open. Bleh. + * + * If the path is NULL use stdin and (to insure against fd leaks) + * assert that the caller passed in -1. + */ +static struct loadedfile * +loadfile(const char *path, int fd) +{ + struct loadedfile *lf; + static long pagesize = 0; + ssize_t result; + size_t bufpos; + + lf = loadedfile_create(path); + + if (path == NULL) { + assert(fd == -1); + fd = STDIN_FILENO; + } else { +#if 0 /* notyet */ + fd = open(path, O_RDONLY); + if (fd < 0) { + ... + Error("%s: %s", path, strerror(errno)); + exit(1); + } +#endif + } + + if (load_getsize(fd, &lf->len) == SUCCESS) { + /* found a size, try mmap */ + if (pagesize == 0) + pagesize = sysconf(_SC_PAGESIZE); + if (pagesize <= 0) { + pagesize = 0x1000; + } + /* round size up to a page */ + lf->maplen = pagesize * ((lf->len + pagesize - 1)/pagesize); + + /* + * XXX hack for dealing with empty files; remove when + * we're no longer limited by interfacing to the old + * logic elsewhere in this file. + */ + if (lf->maplen == 0) { + lf->maplen = pagesize; + } + + /* + * FUTURE: remove PROT_WRITE when the parser no longer + * needs to scribble on the input. + */ + lf->buf = mmap(NULL, lf->maplen, PROT_READ|PROT_WRITE, + MAP_FILE|MAP_COPY, fd, 0); + if (lf->buf != MAP_FAILED) { + /* succeeded */ + if (lf->len == lf->maplen && lf->buf[lf->len - 1] != '\n') { + char *b = bmake_malloc(lf->len + 1); + b[lf->len] = '\n'; + memcpy(b, lf->buf, lf->len++); + munmap(lf->buf, lf->maplen); + lf->maplen = 0; + lf->buf = b; + } + goto done; + } + } + + /* cannot mmap; load the traditional way */ + + lf->maplen = 0; + lf->len = 1024; + lf->buf = bmake_malloc(lf->len); + + bufpos = 0; + while (1) { + assert(bufpos <= lf->len); + if (bufpos == lf->len) { + if (lf->len > SIZE_MAX/2) { + errno = EFBIG; + Error("%s: file too large", path); + exit(1); + } + lf->len *= 2; + lf->buf = bmake_realloc(lf->buf, lf->len); + } + assert(bufpos < lf->len); + result = read(fd, lf->buf + bufpos, lf->len - bufpos); + if (result < 0) { + Error("%s: read error: %s", path, strerror(errno)); + exit(1); + } + if (result == 0) { + break; + } + bufpos += result; + } + assert(bufpos <= lf->len); + lf->len = bufpos; + + /* truncate malloc region to actual length (maybe not useful) */ + if (lf->len > 0) { + /* as for mmap case, ensure trailing \n */ + if (lf->buf[lf->len - 1] != '\n') + lf->len++; + lf->buf = bmake_realloc(lf->buf, lf->len); + lf->buf[lf->len - 1] = '\n'; + } + +done: + if (path != NULL) { + close(fd); + } + return lf; +} + +//////////////////////////////////////////////////////////// +// old code + +/*- + *---------------------------------------------------------------------- + * ParseIsEscaped -- + * Check if the current character is escaped on the current line + * + * Results: + * 0 if the character is not backslash escaped, 1 otherwise + * + * Side Effects: + * None + *---------------------------------------------------------------------- + */ +static int +ParseIsEscaped(const char *line, const char *c) +{ + int active = 0; + for (;;) { + if (line == c) + return active; + if (*--c != '\\') + return active; + active = !active; + } +} + +/*- + *---------------------------------------------------------------------- + * ParseFindKeyword -- + * Look in the table of keywords for one matching the given string. + * + * Input: + * str String to find + * + * Results: + * The index of the keyword, or -1 if it isn't there. + * + * Side Effects: + * None + *---------------------------------------------------------------------- + */ +static int +ParseFindKeyword(const char *str) +{ + int start, end, cur; + int diff; + + start = 0; + end = (sizeof(parseKeywords)/sizeof(parseKeywords[0])) - 1; + + do { + cur = start + ((end - start) / 2); + diff = strcmp(str, parseKeywords[cur].name); + + if (diff == 0) { + return (cur); + } else if (diff < 0) { + end = cur - 1; + } else { + start = cur + 1; + } + } while (start <= end); + return (-1); +} + +/*- + * ParseVErrorInternal -- + * Error message abort function for parsing. Prints out the context + * of the error (line number and file) as well as the message with + * two optional arguments. + * + * Results: + * None + * + * Side Effects: + * "fatals" is incremented if the level is PARSE_FATAL. + */ +/* VARARGS */ +static void +ParseVErrorInternal(FILE *f, const char *cfname, size_t clineno, int type, + const char *fmt, va_list ap) +{ + static Boolean fatal_warning_error_printed = FALSE; + + (void)fprintf(f, "%s: ", progname); + + if (cfname != NULL) { + (void)fprintf(f, "\""); + if (*cfname != '/' && strcmp(cfname, "(stdin)") != 0) { + char *cp, *cp2; + const char *dir, *fname; + + /* + * Nothing is more annoying than not knowing + * which Makefile is the culprit; we try ${.PARSEDIR} + * and apply realpath(3) if not absolute. + */ + dir = Var_Value(".PARSEDIR", VAR_GLOBAL, &cp); + if (dir == NULL) + dir = "."; + if (*dir != '/') { + dir = cp2 = realpath(dir, NULL); + free(cp); + cp = cp2; /* cp2 set to NULL by Var_Value */ + } + fname = Var_Value(".PARSEFILE", VAR_GLOBAL, &cp2); + if (fname == NULL) { + if ((fname = strrchr(cfname, '/'))) + fname++; + else + fname = cfname; + } + (void)fprintf(f, "%s/%s", dir, fname); + free(cp2); + free(cp); + } else + (void)fprintf(f, "%s", cfname); + + (void)fprintf(f, "\" line %d: ", (int)clineno); + } + if (type == PARSE_WARNING) + (void)fprintf(f, "warning: "); + (void)vfprintf(f, fmt, ap); + (void)fprintf(f, "\n"); + (void)fflush(f); + if (type == PARSE_INFO) + return; + if (type == PARSE_FATAL || parseWarnFatal) + fatals += 1; + if (parseWarnFatal && !fatal_warning_error_printed) { + Error("parsing warnings being treated as errors"); + fatal_warning_error_printed = TRUE; + } +} + +/*- + * ParseErrorInternal -- + * Error function + * + * Results: + * None + * + * Side Effects: + * None + */ +/* VARARGS */ +static void +ParseErrorInternal(const char *cfname, size_t clineno, int type, + const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + (void)fflush(stdout); + ParseVErrorInternal(stderr, cfname, clineno, type, fmt, ap); + va_end(ap); + + if (debug_file != stderr && debug_file != stdout) { + va_start(ap, fmt); + ParseVErrorInternal(debug_file, cfname, clineno, type, fmt, ap); + va_end(ap); + } +} + +/*- + * Parse_Error -- + * External interface to ParseErrorInternal; uses the default filename + * Line number. + * + * Results: + * None + * + * Side Effects: + * None + */ +/* VARARGS */ +void +Parse_Error(int type, const char *fmt, ...) +{ + va_list ap; + const char *fname; + size_t lineno; + + if (curFile == NULL) { + fname = NULL; + lineno = 0; + } else { + fname = curFile->fname; + lineno = curFile->lineno; + } + + va_start(ap, fmt); + (void)fflush(stdout); + ParseVErrorInternal(stderr, fname, lineno, type, fmt, ap); + va_end(ap); + + if (debug_file != stderr && debug_file != stdout) { + va_start(ap, fmt); + ParseVErrorInternal(debug_file, fname, lineno, type, fmt, ap); + va_end(ap); + } +} + + +/* + * ParseMessage + * Parse a .info .warning or .error directive + * + * The input is the line minus the ".". We substitute + * variables, print the message and exit(1) (for .error) or just print + * a warning if the directive is malformed. + */ +static Boolean +ParseMessage(char *line) +{ + int mtype; + + switch(*line) { + case 'i': + mtype = PARSE_INFO; + break; + case 'w': + mtype = PARSE_WARNING; + break; + case 'e': + mtype = PARSE_FATAL; + break; + default: + Parse_Error(PARSE_WARNING, "invalid syntax: \".%s\"", line); + return FALSE; + } + + while (isalpha((unsigned char)*line)) + line++; + if (!isspace((unsigned char)*line)) + return FALSE; /* not for us */ + while (isspace((unsigned char)*line)) + line++; + + line = Var_Subst(NULL, line, VAR_CMD, VARF_WANTRES); + Parse_Error(mtype, "%s", line); + free(line); + + if (mtype == PARSE_FATAL) { + /* Terminate immediately. */ + exit(1); + } + return TRUE; +} + +/*- + *--------------------------------------------------------------------- + * ParseLinkSrc -- + * Link the parent node to its new child. Used in a Lst_ForEach by + * ParseDoDependency. If the specType isn't 'Not', the parent + * isn't linked as a parent of the child. + * + * Input: + * pgnp The parent node + * cgpn The child node + * + * Results: + * Always = 0 + * + * Side Effects: + * New elements are added to the parents list of cgn and the + * children list of cgn. the unmade field of pgn is updated + * to reflect the additional child. + *--------------------------------------------------------------------- + */ +static int +ParseLinkSrc(void *pgnp, void *cgnp) +{ + GNode *pgn = (GNode *)pgnp; + GNode *cgn = (GNode *)cgnp; + + if ((pgn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (pgn->cohorts)) + pgn = (GNode *)Lst_Datum(Lst_Last(pgn->cohorts)); + (void)Lst_AtEnd(pgn->children, cgn); + if (specType == Not) + (void)Lst_AtEnd(cgn->parents, pgn); + pgn->unmade += 1; + if (DEBUG(PARSE)) { + fprintf(debug_file, "# %s: added child %s - %s\n", __func__, + pgn->name, cgn->name); + Targ_PrintNode(pgn, 0); + Targ_PrintNode(cgn, 0); + } + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoOp -- + * Apply the parsed operator to the given target node. Used in a + * Lst_ForEach call by ParseDoDependency once all targets have + * been found and their operator parsed. If the previous and new + * operators are incompatible, a major error is taken. + * + * Input: + * gnp The node to which the operator is to be applied + * opp The operator to apply + * + * Results: + * Always 0 + * + * Side Effects: + * The type field of the node is altered to reflect any new bits in + * the op. + *--------------------------------------------------------------------- + */ +static int +ParseDoOp(void *gnp, void *opp) +{ + GNode *gn = (GNode *)gnp; + int op = *(int *)opp; + /* + * If the dependency mask of the operator and the node don't match and + * the node has actually had an operator applied to it before, and + * the operator actually has some dependency information in it, complain. + */ + if (((op & OP_OPMASK) != (gn->type & OP_OPMASK)) && + !OP_NOP(gn->type) && !OP_NOP(op)) + { + Parse_Error(PARSE_FATAL, "Inconsistent operator for %s", gn->name); + return (1); + } + + if ((op == OP_DOUBLEDEP) && ((gn->type & OP_OPMASK) == OP_DOUBLEDEP)) { + /* + * If the node was the object of a :: operator, we need to create a + * new instance of it for the children and commands on this dependency + * line. The new instance is placed on the 'cohorts' list of the + * initial one (note the initial one is not on its own cohorts list) + * and the new instance is linked to all parents of the initial + * instance. + */ + GNode *cohort; + + /* + * Propagate copied bits to the initial node. They'll be propagated + * back to the rest of the cohorts later. + */ + gn->type |= op & ~OP_OPMASK; + + cohort = Targ_FindNode(gn->name, TARG_NOHASH); + if (doing_depend) + ParseMark(cohort); + /* + * Make the cohort invisible as well to avoid duplicating it into + * other variables. True, parents of this target won't tend to do + * anything with their local variables, but better safe than + * sorry. (I think this is pointless now, since the relevant list + * traversals will no longer see this node anyway. -mycroft) + */ + cohort->type = op | OP_INVISIBLE; + (void)Lst_AtEnd(gn->cohorts, cohort); + cohort->centurion = gn; + gn->unmade_cohorts += 1; + snprintf(cohort->cohort_num, sizeof cohort->cohort_num, "#%d", + gn->unmade_cohorts); + } else { + /* + * We don't want to nuke any previous flags (whatever they were) so we + * just OR the new operator into the old + */ + gn->type |= op; + } + + return (0); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoSrc -- + * Given the name of a source, figure out if it is an attribute + * and apply it to the targets if it is. Else decide if there is + * some attribute which should be applied *to* the source because + * of some special target and apply it if so. Otherwise, make the + * source be a child of the targets in the list 'targets' + * + * Input: + * tOp operator (if any) from special targets + * src name of the source to handle + * + * Results: + * None + * + * Side Effects: + * Operator bits may be added to the list of targets or to the source. + * The targets may have a new source added to their lists of children. + *--------------------------------------------------------------------- + */ +static void +ParseDoSrc(int tOp, const char *src) +{ + GNode *gn = NULL; + static int wait_number = 0; + char wait_src[16]; + + if (*src == '.' && isupper ((unsigned char)src[1])) { + int keywd = ParseFindKeyword(src); + if (keywd != -1) { + int op = parseKeywords[keywd].op; + if (op != 0) { + Lst_ForEach(targets, ParseDoOp, &op); + return; + } + if (parseKeywords[keywd].spec == Wait) { + /* + * We add a .WAIT node in the dependency list. + * After any dynamic dependencies (and filename globbing) + * have happened, it is given a dependency on the each + * previous child back to and previous .WAIT node. + * The next child won't be scheduled until the .WAIT node + * is built. + * We give each .WAIT node a unique name (mainly for diag). + */ + snprintf(wait_src, sizeof wait_src, ".WAIT_%u", ++wait_number); + gn = Targ_FindNode(wait_src, TARG_NOHASH); + if (doing_depend) + ParseMark(gn); + gn->type = OP_WAIT | OP_PHONY | OP_DEPENDS | OP_NOTMAIN; + Lst_ForEach(targets, ParseLinkSrc, gn); + return; + } + } + } + + switch (specType) { + case Main: + /* + * If we have noted the existence of a .MAIN, it means we need + * to add the sources of said target to the list of things + * to create. The string 'src' is likely to be free, so we + * must make a new copy of it. Note that this will only be + * invoked if the user didn't specify a target on the command + * line. This is to allow #ifmake's to succeed, or something... + */ + (void)Lst_AtEnd(create, bmake_strdup(src)); + /* + * Add the name to the .TARGETS variable as well, so the user can + * employ that, if desired. + */ + Var_Append(".TARGETS", src, VAR_GLOBAL); + return; + + case Order: + /* + * Create proper predecessor/successor links between the previous + * source and the current one. + */ + gn = Targ_FindNode(src, TARG_CREATE); + if (doing_depend) + ParseMark(gn); + if (predecessor != NULL) { + (void)Lst_AtEnd(predecessor->order_succ, gn); + (void)Lst_AtEnd(gn->order_pred, predecessor); + if (DEBUG(PARSE)) { + fprintf(debug_file, "# %s: added Order dependency %s - %s\n", + __func__, predecessor->name, gn->name); + Targ_PrintNode(predecessor, 0); + Targ_PrintNode(gn, 0); + } + } + /* + * The current source now becomes the predecessor for the next one. + */ + predecessor = gn; + break; + + default: + /* + * If the source is not an attribute, we need to find/create + * a node for it. After that we can apply any operator to it + * from a special target or link it to its parents, as + * appropriate. + * + * In the case of a source that was the object of a :: operator, + * the attribute is applied to all of its instances (as kept in + * the 'cohorts' list of the node) or all the cohorts are linked + * to all the targets. + */ + + /* Find/create the 'src' node and attach to all targets */ + gn = Targ_FindNode(src, TARG_CREATE); + if (doing_depend) + ParseMark(gn); + if (tOp) { + gn->type |= tOp; + } else { + Lst_ForEach(targets, ParseLinkSrc, gn); + } + break; + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFindMain -- + * Find a real target in the list and set it to be the main one. + * Called by ParseDoDependency when a main target hasn't been found + * yet. + * + * Input: + * gnp Node to examine + * + * Results: + * 0 if main not found yet, 1 if it is. + * + * Side Effects: + * mainNode is changed and Targ_SetMain is called. + * + *----------------------------------------------------------------------- + */ +static int +ParseFindMain(void *gnp, void *dummy MAKE_ATTR_UNUSED) +{ + GNode *gn = (GNode *)gnp; + if ((gn->type & OP_NOTARGET) == 0) { + mainNode = gn; + Targ_SetMain(gn); + return 1; + } else { + return 0; + } +} + +/*- + *----------------------------------------------------------------------- + * ParseAddDir -- + * Front-end for Dir_AddDir to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_AddDir. + * + *----------------------------------------------------------------------- + */ +static int +ParseAddDir(void *path, void *name) +{ + (void)Dir_AddDir((Lst) path, (char *)name); + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseClearPath -- + * Front-end for Dir_ClearPath to make sure Lst_ForEach keeps going + * + * Results: + * === 0 + * + * Side Effects: + * See Dir_ClearPath + * + *----------------------------------------------------------------------- + */ +static int +ParseClearPath(void *path, void *dummy MAKE_ATTR_UNUSED) +{ + Dir_ClearPath((Lst) path); + return 0; +} + +/*- + *--------------------------------------------------------------------- + * ParseDoDependency -- + * Parse the dependency line in line. + * + * Input: + * line the line to parse + * + * Results: + * None + * + * Side Effects: + * The nodes of the sources are linked as children to the nodes of the + * targets. Some nodes may be created. + * + * We parse a dependency line by first extracting words from the line and + * finding nodes in the list of all targets with that name. This is done + * until a character is encountered which is an operator character. Currently + * these are only ! and :. At this point the operator is parsed and the + * pointer into the line advanced until the first source is encountered. + * The parsed operator is applied to each node in the 'targets' list, + * which is where the nodes found for the targets are kept, by means of + * the ParseDoOp function. + * The sources are read in much the same way as the targets were except + * that now they are expanded using the wildcarding scheme of the C-Shell + * and all instances of the resulting words in the list of all targets + * are found. Each of the resulting nodes is then linked to each of the + * targets as one of its children. + * Certain targets are handled specially. These are the ones detailed + * by the specType variable. + * The storing of transformation rules is also taken care of here. + * A target is recognized as a transformation rule by calling + * Suff_IsTransform. If it is a transformation rule, its node is gotten + * from the suffix module via Suff_AddTransform rather than the standard + * Targ_FindNode in the target module. + *--------------------------------------------------------------------- + */ +static void +ParseDoDependency(char *line) +{ + char *cp; /* our current position */ + GNode *gn = NULL; /* a general purpose temporary node */ + int op; /* the operator on the line */ + char savec; /* a place to save a character */ + Lst paths; /* List of search paths to alter when parsing + * a list of .PATH targets */ + int tOp; /* operator from special target */ + Lst sources; /* list of archive source names after + * expansion */ + Lst curTargs; /* list of target names to be found and added + * to the targets list */ + char *lstart = line; + + if (DEBUG(PARSE)) + fprintf(debug_file, "ParseDoDependency(%s)\n", line); + tOp = 0; + + specType = Not; + paths = NULL; + + curTargs = Lst_Init(FALSE); + + /* + * First, grind through the targets. + */ + + do { + /* + * Here LINE points to the beginning of the next word, and + * LSTART points to the actual beginning of the line. + */ + + /* Find the end of the next word. */ + for (cp = line; *cp && (ParseIsEscaped(lstart, cp) || + !(isspace((unsigned char)*cp) || + *cp == '!' || *cp == ':' || *cp == LPAREN)); + cp++) { + if (*cp == '$') { + /* + * Must be a dynamic source (would have been expanded + * otherwise), so call the Var module to parse the puppy + * so we can safely advance beyond it...There should be + * no errors in this, as they would have been discovered + * in the initial Var_Subst and we wouldn't be here. + */ + int length; + void *freeIt; + + (void)Var_Parse(cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES, + &length, &freeIt); + free(freeIt); + cp += length-1; + } + } + + /* + * If the word is followed by a left parenthesis, it's the + * name of an object file inside an archive (ar file). + */ + if (!ParseIsEscaped(lstart, cp) && *cp == LPAREN) { + /* + * Archives must be handled specially to make sure the OP_ARCHV + * flag is set in their 'type' field, for one thing, and because + * things like "archive(file1.o file2.o file3.o)" are permissible. + * Arch_ParseArchive will set 'line' to be the first non-blank + * after the archive-spec. It creates/finds nodes for the members + * and places them on the given list, returning SUCCESS if all + * went well and FAILURE if there was an error in the + * specification. On error, line should remain untouched. + */ + if (Arch_ParseArchive(&line, targets, VAR_CMD) != SUCCESS) { + Parse_Error(PARSE_FATAL, + "Error in archive specification: \"%s\"", line); + goto out; + } else { + /* Done with this word; on to the next. */ + cp = line; + continue; + } + } + + if (!*cp) { + /* + * We got to the end of the line while we were still + * looking at targets. + * + * Ending a dependency line without an operator is a Bozo + * no-no. As a heuristic, this is also often triggered by + * undetected conflicts from cvs/rcs merges. + */ + if ((strncmp(line, "<<<<<<", 6) == 0) || + (strncmp(line, "======", 6) == 0) || + (strncmp(line, ">>>>>>", 6) == 0)) + Parse_Error(PARSE_FATAL, + "Makefile appears to contain unresolved cvs/rcs/??? merge conflicts"); + else + Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" + : "Need an operator"); + goto out; + } + + /* Insert a null terminator. */ + savec = *cp; + *cp = '\0'; + + /* + * Got the word. See if it's a special target and if so set + * specType to match it. + */ + if (*line == '.' && isupper ((unsigned char)line[1])) { + /* + * See if the target is a special target that must have it + * or its sources handled specially. + */ + int keywd = ParseFindKeyword(line); + if (keywd != -1) { + if (specType == ExPath && parseKeywords[keywd].spec != ExPath) { + Parse_Error(PARSE_FATAL, "Mismatched special targets"); + goto out; + } + + specType = parseKeywords[keywd].spec; + tOp = parseKeywords[keywd].op; + + /* + * Certain special targets have special semantics: + * .PATH Have to set the dirSearchPath + * variable too + * .MAIN Its sources are only used if + * nothing has been specified to + * create. + * .DEFAULT Need to create a node to hang + * commands on, but we don't want + * it in the graph, nor do we want + * it to be the Main Target, so we + * create it, set OP_NOTMAIN and + * add it to the list, setting + * DEFAULT to the new node for + * later use. We claim the node is + * A transformation rule to make + * life easier later, when we'll + * use Make_HandleUse to actually + * apply the .DEFAULT commands. + * .PHONY The list of targets + * .NOPATH Don't search for file in the path + * .STALE + * .BEGIN + * .END + * .ERROR + * .DELETE_ON_ERROR + * .INTERRUPT Are not to be considered the + * main target. + * .NOTPARALLEL Make only one target at a time. + * .SINGLESHELL Create a shell for each command. + * .ORDER Must set initial predecessor to NULL + */ + switch (specType) { + case ExPath: + if (paths == NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, dirSearchPath); + break; + case Main: + if (!Lst_IsEmpty(create)) { + specType = Not; + } + break; + case Begin: + case End: + case Stale: + case dotError: + case Interrupt: + gn = Targ_FindNode(line, TARG_CREATE); + if (doing_depend) + ParseMark(gn); + gn->type |= OP_NOTMAIN|OP_SPECIAL; + (void)Lst_AtEnd(targets, gn); + break; + case Default: + gn = Targ_NewGN(".DEFAULT"); + gn->type |= (OP_NOTMAIN|OP_TRANSFORM); + (void)Lst_AtEnd(targets, gn); + DEFAULT = gn; + break; + case DeleteOnError: + deleteOnError = TRUE; + break; + case NotParallel: + maxJobs = 1; + break; + case SingleShell: + compatMake = TRUE; + break; + case Order: + predecessor = NULL; + break; + default: + break; + } + } else if (strncmp(line, ".PATH", 5) == 0) { + /* + * .PATH has to be handled specially. + * Call on the suffix module to give us a path to + * modify. + */ + Lst path; + + specType = ExPath; + path = Suff_GetPath(&line[5]); + if (path == NULL) { + Parse_Error(PARSE_FATAL, + "Suffix '%s' not defined (yet)", + &line[5]); + goto out; + } else { + if (paths == NULL) { + paths = Lst_Init(FALSE); + } + (void)Lst_AtEnd(paths, path); + } + } + } + + /* + * Have word in line. Get or create its node and stick it at + * the end of the targets list + */ + if ((specType == Not) && (*line != '\0')) { + if (Dir_HasWildcards(line)) { + /* + * Targets are to be sought only in the current directory, + * so create an empty path for the thing. Note we need to + * use Dir_Destroy in the destruction of the path as the + * Dir module could have added a directory to the path... + */ + Lst emptyPath = Lst_Init(FALSE); + + Dir_Expand(line, emptyPath, curTargs); + + Lst_Destroy(emptyPath, Dir_Destroy); + } else { + /* + * No wildcards, but we want to avoid code duplication, + * so create a list with the word on it. + */ + (void)Lst_AtEnd(curTargs, line); + } + + /* Apply the targets. */ + + while(!Lst_IsEmpty(curTargs)) { + char *targName = (char *)Lst_DeQueue(curTargs); + + if (!Suff_IsTransform (targName)) { + gn = Targ_FindNode(targName, TARG_CREATE); + } else { + gn = Suff_AddTransform(targName); + } + if (doing_depend) + ParseMark(gn); + + (void)Lst_AtEnd(targets, gn); + } + } else if (specType == ExPath && *line != '.' && *line != '\0') { + Parse_Error(PARSE_WARNING, "Extra target (%s) ignored", line); + } + + /* Don't need the inserted null terminator any more. */ + *cp = savec; + + /* + * If it is a special type and not .PATH, it's the only target we + * allow on this line... + */ + if (specType != Not && specType != ExPath) { + Boolean warning = FALSE; + + while (*cp && (ParseIsEscaped(lstart, cp) || + ((*cp != '!') && (*cp != ':')))) { + if (ParseIsEscaped(lstart, cp) || + (*cp != ' ' && *cp != '\t')) { + warning = TRUE; + } + cp++; + } + if (warning) { + Parse_Error(PARSE_WARNING, "Extra target ignored"); + } + } else { + while (*cp && isspace ((unsigned char)*cp)) { + cp++; + } + } + line = cp; + } while (*line && (ParseIsEscaped(lstart, line) || + ((*line != '!') && (*line != ':')))); + + /* + * Don't need the list of target names anymore... + */ + Lst_Destroy(curTargs, NULL); + curTargs = NULL; + + if (!Lst_IsEmpty(targets)) { + switch(specType) { + default: + Parse_Error(PARSE_WARNING, "Special and mundane targets don't mix. Mundane ones ignored"); + break; + case Default: + case Stale: + case Begin: + case End: + case dotError: + case Interrupt: + /* + * These four create nodes on which to hang commands, so + * targets shouldn't be empty... + */ + case Not: + /* + * Nothing special here -- targets can be empty if it wants. + */ + break; + } + } + + /* + * Have now parsed all the target names. Must parse the operator next. The + * result is left in op . + */ + if (*cp == '!') { + op = OP_FORCE; + } else if (*cp == ':') { + if (cp[1] == ':') { + op = OP_DOUBLEDEP; + cp++; + } else { + op = OP_DEPENDS; + } + } else { + Parse_Error(PARSE_FATAL, lstart[0] == '.' ? "Unknown directive" + : "Missing dependency operator"); + goto out; + } + + /* Advance beyond the operator */ + cp++; + + /* + * Apply the operator to the target. This is how we remember which + * operator a target was defined with. It fails if the operator + * used isn't consistent across all references. + */ + Lst_ForEach(targets, ParseDoOp, &op); + + /* + * Onward to the sources. + * + * LINE will now point to the first source word, if any, or the + * end of the string if not. + */ + while (*cp && isspace ((unsigned char)*cp)) { + cp++; + } + line = cp; + + /* + * Several special targets take different actions if present with no + * sources: + * a .SUFFIXES line with no sources clears out all old suffixes + * a .PRECIOUS line makes all targets precious + * a .IGNORE line ignores errors for all targets + * a .SILENT line creates silence when making all targets + * a .PATH removes all directories from the search path(s). + */ + if (!*line) { + switch (specType) { + case Suffixes: + Suff_ClearSuffixes(); + break; + case Precious: + allPrecious = TRUE; + break; + case Ignore: + ignoreErrors = TRUE; + break; + case Silent: + beSilent = TRUE; + break; + case ExPath: + Lst_ForEach(paths, ParseClearPath, NULL); + Dir_SetPATH(); + break; +#ifdef POSIX + case Posix: + Var_Set("%POSIX", "1003.2", VAR_GLOBAL, 0); + break; +#endif + default: + break; + } + } else if (specType == MFlags) { + /* + * Call on functions in main.c to deal with these arguments and + * set the initial character to a null-character so the loop to + * get sources won't get anything + */ + Main_ParseArgLine(line); + *line = '\0'; + } else if (specType == ExShell) { + if (Job_ParseShell(line) != SUCCESS) { + Parse_Error(PARSE_FATAL, "improper shell specification"); + goto out; + } + *line = '\0'; + } else if ((specType == NotParallel) || (specType == SingleShell) || + (specType == DeleteOnError)) { + *line = '\0'; + } + + /* + * NOW GO FOR THE SOURCES + */ + if ((specType == Suffixes) || (specType == ExPath) || + (specType == Includes) || (specType == Libs) || + (specType == Null) || (specType == ExObjdir)) + { + while (*line) { + /* + * If the target was one that doesn't take files as its sources + * but takes something like suffixes, we take each + * space-separated word on the line as a something and deal + * with it accordingly. + * + * If the target was .SUFFIXES, we take each source as a + * suffix and add it to the list of suffixes maintained by the + * Suff module. + * + * If the target was a .PATH, we add the source as a directory + * to search on the search path. + * + * If it was .INCLUDES, the source is taken to be the suffix of + * files which will be #included and whose search path should + * be present in the .INCLUDES variable. + * + * If it was .LIBS, the source is taken to be the suffix of + * files which are considered libraries and whose search path + * should be present in the .LIBS variable. + * + * If it was .NULL, the source is the suffix to use when a file + * has no valid suffix. + * + * If it was .OBJDIR, the source is a new definition for .OBJDIR, + * and will cause make to do a new chdir to that path. + */ + while (*cp && !isspace ((unsigned char)*cp)) { + cp++; + } + savec = *cp; + *cp = '\0'; + switch (specType) { + case Suffixes: + Suff_AddSuffix(line, &mainNode); + break; + case ExPath: + Lst_ForEach(paths, ParseAddDir, line); + break; + case Includes: + Suff_AddInclude(line); + break; + case Libs: + Suff_AddLib(line); + break; + case Null: + Suff_SetNull(line); + break; + case ExObjdir: + Main_SetObjdir("%s", line); + break; + default: + break; + } + *cp = savec; + if (savec != '\0') { + cp++; + } + while (*cp && isspace ((unsigned char)*cp)) { + cp++; + } + line = cp; + } + if (paths) { + Lst_Destroy(paths, NULL); + paths = NULL; + } + if (specType == ExPath) + Dir_SetPATH(); + } else { + assert(paths == NULL); + while (*line) { + /* + * The targets take real sources, so we must beware of archive + * specifications (i.e. things with left parentheses in them) + * and handle them accordingly. + */ + for (; *cp && !isspace ((unsigned char)*cp); cp++) { + if ((*cp == LPAREN) && (cp > line) && (cp[-1] != '$')) { + /* + * Only stop for a left parenthesis if it isn't at the + * start of a word (that'll be for variable changes + * later) and isn't preceded by a dollar sign (a dynamic + * source). + */ + break; + } + } + + if (*cp == LPAREN) { + sources = Lst_Init(FALSE); + if (Arch_ParseArchive(&line, sources, VAR_CMD) != SUCCESS) { + Parse_Error(PARSE_FATAL, + "Error in source archive spec \"%s\"", line); + goto out; + } + + while (!Lst_IsEmpty (sources)) { + gn = (GNode *)Lst_DeQueue(sources); + ParseDoSrc(tOp, gn->name); + } + Lst_Destroy(sources, NULL); + cp = line; + } else { + if (*cp) { + *cp = '\0'; + cp += 1; + } + + ParseDoSrc(tOp, line); + } + while (*cp && isspace ((unsigned char)*cp)) { + cp++; + } + line = cp; + } + } + + if (mainNode == NULL) { + /* + * If we have yet to decide on a main target to make, in the + * absence of any user input, we want the first target on + * the first dependency line that is actually a real target + * (i.e. isn't a .USE or .EXEC rule) to be made. + */ + Lst_ForEach(targets, ParseFindMain, NULL); + } + +out: + assert(paths == NULL); + if (curTargs) + Lst_Destroy(curTargs, NULL); +} + +/*- + *--------------------------------------------------------------------- + * Parse_IsVar -- + * Return TRUE if the passed line is a variable assignment. A variable + * assignment consists of a single word followed by optional whitespace + * followed by either a += or an = operator. + * This function is used both by the Parse_File function and main when + * parsing the command-line arguments. + * + * Input: + * line the line to check + * + * Results: + * TRUE if it is. FALSE if it ain't + * + * Side Effects: + * none + *--------------------------------------------------------------------- + */ +Boolean +Parse_IsVar(char *line) +{ + Boolean wasSpace = FALSE; /* set TRUE if found a space */ + char ch; + int level = 0; +#define ISEQOPERATOR(c) \ + (((c) == '+') || ((c) == ':') || ((c) == '?') || ((c) == '!')) + + /* + * Skip to variable name + */ + for (;(*line == ' ') || (*line == '\t'); line++) + continue; + + /* Scan for one of the assignment operators outside a variable expansion */ + while ((ch = *line++) != 0) { + if (ch == '(' || ch == '{') { + level++; + continue; + } + if (ch == ')' || ch == '}') { + level--; + continue; + } + if (level != 0) + continue; + while (ch == ' ' || ch == '\t') { + ch = *line++; + wasSpace = TRUE; + } +#ifdef SUNSHCMD + if (ch == ':' && strncmp(line, "sh", 2) == 0) { + line += 2; + continue; + } +#endif + if (ch == '=') + return TRUE; + if (*line == '=' && ISEQOPERATOR(ch)) + return TRUE; + if (wasSpace) + return FALSE; + } + + return FALSE; +} + +/*- + *--------------------------------------------------------------------- + * Parse_DoVar -- + * Take the variable assignment in the passed line and do it in the + * global context. + * + * Note: There is a lexical ambiguity with assignment modifier characters + * in variable names. This routine interprets the character before the = + * as a modifier. Therefore, an assignment like + * C++=/usr/bin/CC + * is interpreted as "C+ +=" instead of "C++ =". + * + * Input: + * line a line guaranteed to be a variable assignment. + * This reduces error checks + * ctxt Context in which to do the assignment + * + * Results: + * none + * + * Side Effects: + * the variable structure of the given variable name is altered in the + * global context. + *--------------------------------------------------------------------- + */ +void +Parse_DoVar(char *line, GNode *ctxt) +{ + char *cp; /* pointer into line */ + enum { + VAR_SUBST, VAR_APPEND, VAR_SHELL, VAR_NORMAL + } type; /* Type of assignment */ + char *opc; /* ptr to operator character to + * null-terminate the variable name */ + Boolean freeCp = FALSE; /* TRUE if cp needs to be freed, + * i.e. if any variable expansion was + * performed */ + int depth; + + /* + * Skip to variable name + */ + while ((*line == ' ') || (*line == '\t')) { + line++; + } + + /* + * Skip to operator character, nulling out whitespace as we go + * XXX Rather than counting () and {} we should look for $ and + * then expand the variable. + */ + for (depth = 0, cp = line + 1; depth > 0 || *cp != '='; cp++) { + if (*cp == '(' || *cp == '{') { + depth++; + continue; + } + if (*cp == ')' || *cp == '}') { + depth--; + continue; + } + if (depth == 0 && isspace ((unsigned char)*cp)) { + *cp = '\0'; + } + } + opc = cp-1; /* operator is the previous character */ + *cp++ = '\0'; /* nuke the = */ + + /* + * Check operator type + */ + switch (*opc) { + case '+': + type = VAR_APPEND; + *opc = '\0'; + break; + + case '?': + /* + * If the variable already has a value, we don't do anything. + */ + *opc = '\0'; + if (Var_Exists(line, ctxt)) { + return; + } else { + type = VAR_NORMAL; + } + break; + + case ':': + type = VAR_SUBST; + *opc = '\0'; + break; + + case '!': + type = VAR_SHELL; + *opc = '\0'; + break; + + default: +#ifdef SUNSHCMD + while (opc > line && *opc != ':') + opc--; + + if (strncmp(opc, ":sh", 3) == 0) { + type = VAR_SHELL; + *opc = '\0'; + break; + } +#endif + type = VAR_NORMAL; + break; + } + + while (isspace ((unsigned char)*cp)) { + cp++; + } + + if (type == VAR_APPEND) { + Var_Append(line, cp, ctxt); + } else if (type == VAR_SUBST) { + /* + * Allow variables in the old value to be undefined, but leave their + * invocation alone -- this is done by forcing oldVars to be false. + * XXX: This can cause recursive variables, but that's not hard to do, + * and this allows someone to do something like + * + * CFLAGS = $(.INCLUDES) + * CFLAGS := -I.. $(CFLAGS) + * + * And not get an error. + */ + Boolean oldOldVars = oldVars; + + oldVars = FALSE; + + /* + * make sure that we set the variable the first time to nothing + * so that it gets substituted! + */ + if (!Var_Exists(line, ctxt)) + Var_Set(line, "", ctxt, 0); + + cp = Var_Subst(NULL, cp, ctxt, VARF_WANTRES|VARF_ASSIGN); + oldVars = oldOldVars; + freeCp = TRUE; + + Var_Set(line, cp, ctxt, 0); + } else if (type == VAR_SHELL) { + char *res; + const char *error; + + if (strchr(cp, '$') != NULL) { + /* + * There's a dollar sign in the command, so perform variable + * expansion on the whole thing. The resulting string will need + * freeing when we're done, so set freeCmd to TRUE. + */ + cp = Var_Subst(NULL, cp, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES); + freeCp = TRUE; + } + + res = Cmd_Exec(cp, &error); + Var_Set(line, res, ctxt, 0); + free(res); + + if (error) + Parse_Error(PARSE_WARNING, error, cp); + } else { + /* + * Normal assignment -- just do it. + */ + Var_Set(line, cp, ctxt, 0); + } + if (strcmp(line, MAKEOVERRIDES) == 0) + Main_ExportMAKEFLAGS(FALSE); /* re-export MAKEFLAGS */ + else if (strcmp(line, ".CURDIR") == 0) { + /* + * Somone is being (too?) clever... + * Let's pretend they know what they are doing and + * re-initialize the 'cur' Path. + */ + Dir_InitCur(cp); + Dir_SetPATH(); + } else if (strcmp(line, MAKE_JOB_PREFIX) == 0) { + Job_SetPrefix(); + } else if (strcmp(line, MAKE_EXPORTED) == 0) { + Var_Export(cp, 0); + } + if (freeCp) + free(cp); +} + + +/* + * ParseMaybeSubMake -- + * Scan the command string to see if it a possible submake node + * Input: + * cmd the command to scan + * Results: + * TRUE if the command is possibly a submake, FALSE if not. + */ +static Boolean +ParseMaybeSubMake(const char *cmd) +{ + size_t i; + static struct { + const char *name; + size_t len; + } vals[] = { +#define MKV(A) { A, sizeof(A) - 1 } + MKV("${MAKE}"), + MKV("${.MAKE}"), + MKV("$(MAKE)"), + MKV("$(.MAKE)"), + MKV("make"), + }; + for (i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) { + char *ptr; + if ((ptr = strstr(cmd, vals[i].name)) == NULL) + continue; + if ((ptr == cmd || !isalnum((unsigned char)ptr[-1])) + && !isalnum((unsigned char)ptr[vals[i].len])) + return TRUE; + } + return FALSE; +} + +/*- + * ParseAddCmd -- + * Lst_ForEach function to add a command line to all targets + * + * Input: + * gnp the node to which the command is to be added + * cmd the command to add + * + * Results: + * Always 0 + * + * Side Effects: + * A new element is added to the commands list of the node, + * and the node can be marked as a submake node if the command is + * determined to be that. + */ +static int +ParseAddCmd(void *gnp, void *cmd) +{ + GNode *gn = (GNode *)gnp; + + /* Add to last (ie current) cohort for :: targets */ + if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) + gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); + + /* if target already supplied, ignore commands */ + if (!(gn->type & OP_HAS_COMMANDS)) { + (void)Lst_AtEnd(gn->commands, cmd); + if (ParseMaybeSubMake(cmd)) + gn->type |= OP_SUBMAKE; + ParseMark(gn); + } else { +#ifdef notyet + /* XXX: We cannot do this until we fix the tree */ + (void)Lst_AtEnd(gn->commands, cmd); + Parse_Error(PARSE_WARNING, + "overriding commands for target \"%s\"; " + "previous commands defined at %s: %d ignored", + gn->name, gn->fname, gn->lineno); +#else + Parse_Error(PARSE_WARNING, + "duplicate script for target \"%s\" ignored", + gn->name); + ParseErrorInternal(gn->fname, gn->lineno, PARSE_WARNING, + "using previous script for \"%s\" defined here", + gn->name); +#endif + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * ParseHasCommands -- + * Callback procedure for Parse_File when destroying the list of + * targets on the last dependency line. Marks a target as already + * having commands if it does, to keep from having shell commands + * on multiple dependency lines. + * + * Input: + * gnp Node to examine + * + * Results: + * None + * + * Side Effects: + * OP_HAS_COMMANDS may be set for the target. + * + *----------------------------------------------------------------------- + */ +static void +ParseHasCommands(void *gnp) +{ + GNode *gn = (GNode *)gnp; + if (!Lst_IsEmpty(gn->commands)) { + gn->type |= OP_HAS_COMMANDS; + } +} + +/*- + *----------------------------------------------------------------------- + * Parse_AddIncludeDir -- + * Add a directory to the path searched for included makefiles + * bracketed by double-quotes. Used by functions in main.c + * + * Input: + * dir The name of the directory to add + * + * Results: + * None. + * + * Side Effects: + * The directory is appended to the list. + * + *----------------------------------------------------------------------- + */ +void +Parse_AddIncludeDir(char *dir) +{ + (void)Dir_AddDir(parseIncPath, dir); +} + +/*- + *--------------------------------------------------------------------- + * ParseDoInclude -- + * Push to another file. + * + * The input is the line minus the `.'. A file spec is a string + * enclosed in <> or "". The former is looked for only in sysIncPath. + * The latter in . and the directories specified by -I command line + * options + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ + +static void +Parse_include_file(char *file, Boolean isSystem, Boolean depinc, int silent) +{ + struct loadedfile *lf; + char *fullname; /* full pathname of file */ + char *newName; + char *prefEnd, *incdir; + int fd; + int i; + + /* + * Now we know the file's name and its search path, we attempt to + * find the durn thing. A return of NULL indicates the file don't + * exist. + */ + fullname = file[0] == '/' ? bmake_strdup(file) : NULL; + + if (fullname == NULL && !isSystem) { + /* + * Include files contained in double-quotes are first searched for + * relative to the including file's location. We don't want to + * cd there, of course, so we just tack on the old file's + * leading path components and call Dir_FindFile to see if + * we can locate the beast. + */ + + incdir = bmake_strdup(curFile->fname); + prefEnd = strrchr(incdir, '/'); + if (prefEnd != NULL) { + *prefEnd = '\0'; + /* Now do lexical processing of leading "../" on the filename */ + for (i = 0; strncmp(file + i, "../", 3) == 0; i += 3) { + prefEnd = strrchr(incdir + 1, '/'); + if (prefEnd == NULL || strcmp(prefEnd, "/..") == 0) + break; + *prefEnd = '\0'; + } + newName = str_concat(incdir, file + i, STR_ADDSLASH); + fullname = Dir_FindFile(newName, parseIncPath); + if (fullname == NULL) + fullname = Dir_FindFile(newName, dirSearchPath); + free(newName); + } + free(incdir); + + if (fullname == NULL) { + /* + * Makefile wasn't found in same directory as included makefile. + * Search for it first on the -I search path, + * then on the .PATH search path, if not found in a -I directory. + * If we have a suffix specific path we should use that. + */ + char *suff; + Lst suffPath = NULL; + + if ((suff = strrchr(file, '.'))) { + suffPath = Suff_GetPath(suff); + if (suffPath != NULL) { + fullname = Dir_FindFile(file, suffPath); + } + } + if (fullname == NULL) { + fullname = Dir_FindFile(file, parseIncPath); + if (fullname == NULL) { + fullname = Dir_FindFile(file, dirSearchPath); + } + } + } + } + + /* Looking for a system file or file still not found */ + if (fullname == NULL) { + /* + * Look for it on the system path + */ + fullname = Dir_FindFile(file, + Lst_IsEmpty(sysIncPath) ? defIncPath : sysIncPath); + } + + if (fullname == NULL) { + if (!silent) + Parse_Error(PARSE_FATAL, "Could not find %s", file); + return; + } + + /* Actually open the file... */ + fd = open(fullname, O_RDONLY); + if (fd == -1) { + if (!silent) + Parse_Error(PARSE_FATAL, "Cannot open %s", fullname); + free(fullname); + return; + } + + /* load it */ + lf = loadfile(fullname, fd); + + ParseSetIncludedFile(); + /* Start reading from this file next */ + Parse_SetInput(fullname, 0, -1, loadedfile_nextbuf, lf); + curFile->lf = lf; + if (depinc) + doing_depend = depinc; /* only turn it on */ +} + +static void +ParseDoInclude(char *line) +{ + char endc; /* the character which ends the file spec */ + char *cp; /* current position in file spec */ + int silent = (*line != 'i') ? 1 : 0; + char *file = &line[7 + silent]; + + /* Skip to delimiter character so we know where to look */ + while (*file == ' ' || *file == '\t') + file++; + + if (*file != '"' && *file != '<') { + Parse_Error(PARSE_FATAL, + ".include filename must be delimited by '\"' or '<'"); + return; + } + + /* + * Set the search path on which to find the include file based on the + * characters which bracket its name. Angle-brackets imply it's + * a system Makefile while double-quotes imply it's a user makefile + */ + if (*file == '<') { + endc = '>'; + } else { + endc = '"'; + } + + /* Skip to matching delimiter */ + for (cp = ++file; *cp && *cp != endc; cp++) + continue; + + if (*cp != endc) { + Parse_Error(PARSE_FATAL, + "Unclosed %cinclude filename. '%c' expected", + '.', endc); + return; + } + *cp = '\0'; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + file = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES); + + Parse_include_file(file, endc == '>', (*line == 'd'), silent); + free(file); +} + + +/*- + *--------------------------------------------------------------------- + * ParseSetIncludedFile -- + * Set the .INCLUDEDFROMFILE variable to the contents of .PARSEFILE + * and the .INCLUDEDFROMDIR variable to the contents of .PARSEDIR + * + * Results: + * None + * + * Side Effects: + * The .INCLUDEDFROMFILE variable is overwritten by the contents + * of .PARSEFILE and the .INCLUDEDFROMDIR variable is overwriten + * by the contents of .PARSEDIR + *--------------------------------------------------------------------- + */ +static void +ParseSetIncludedFile(void) +{ + char *pf, *fp = NULL; + char *pd, *dp = NULL; + + pf = Var_Value(".PARSEFILE", VAR_GLOBAL, &fp); + Var_Set(".INCLUDEDFROMFILE", pf, VAR_GLOBAL, 0); + pd = Var_Value(".PARSEDIR", VAR_GLOBAL, &dp); + Var_Set(".INCLUDEDFROMDIR", pd, VAR_GLOBAL, 0); + + if (DEBUG(PARSE)) + fprintf(debug_file, "%s: ${.INCLUDEDFROMDIR} = `%s' " + "${.INCLUDEDFROMFILE} = `%s'\n", __func__, pd, pf); + + free(fp); + free(dp); +} +/*- + *--------------------------------------------------------------------- + * ParseSetParseFile -- + * Set the .PARSEDIR and .PARSEFILE variables to the dirname and + * basename of the given filename + * + * Results: + * None + * + * Side Effects: + * The .PARSEDIR and .PARSEFILE variables are overwritten by the + * dirname and basename of the given filename. + *--------------------------------------------------------------------- + */ +static void +ParseSetParseFile(const char *filename) +{ + char *slash, *dirname; + const char *pd, *pf; + int len; + + slash = strrchr(filename, '/'); + if (slash == NULL) { + Var_Set(".PARSEDIR", pd = curdir, VAR_GLOBAL, 0); + Var_Set(".PARSEFILE", pf = filename, VAR_GLOBAL, 0); + dirname= NULL; + } else { + len = slash - filename; + dirname = bmake_malloc(len + 1); + memcpy(dirname, filename, len); + dirname[len] = '\0'; + Var_Set(".PARSEDIR", pd = dirname, VAR_GLOBAL, 0); + Var_Set(".PARSEFILE", pf = slash + 1, VAR_GLOBAL, 0); + } + if (DEBUG(PARSE)) + fprintf(debug_file, "%s: ${.PARSEDIR} = `%s' ${.PARSEFILE} = `%s'\n", + __func__, pd, pf); + free(dirname); +} + +/* + * Track the makefiles we read - so makefiles can + * set dependencies on them. + * Avoid adding anything more than once. + */ + +static void +ParseTrackInput(const char *name) +{ + char *old; + char *ep; + char *fp = NULL; + size_t name_len = strlen(name); + + old = Var_Value(MAKE_MAKEFILES, VAR_GLOBAL, &fp); + if (old) { + ep = old + strlen(old) - name_len; + /* does it contain name? */ + for (; old != NULL; old = strchr(old, ' ')) { + if (*old == ' ') + old++; + if (old >= ep) + break; /* cannot contain name */ + if (memcmp(old, name, name_len) == 0 + && (old[name_len] == 0 || old[name_len] == ' ')) + goto cleanup; + } + } + Var_Append (MAKE_MAKEFILES, name, VAR_GLOBAL); + cleanup: + if (fp) { + free(fp); + } +} + + +/*- + *--------------------------------------------------------------------- + * Parse_setInput -- + * Start Parsing from the given source + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFile are altered for the new file + *--------------------------------------------------------------------- + */ +void +Parse_SetInput(const char *name, int line, int fd, + char *(*nextbuf)(void *, size_t *), void *arg) +{ + char *buf; + size_t len; + + if (name == NULL) + name = curFile->fname; + else + ParseTrackInput(name); + + if (DEBUG(PARSE)) + fprintf(debug_file, "%s: file %s, line %d, fd %d, nextbuf %p, arg %p\n", + __func__, name, line, fd, nextbuf, arg); + + if (fd == -1 && nextbuf == NULL) + /* sanity */ + return; + + if (curFile != NULL) + /* Save exiting file info */ + Lst_AtFront(includes, curFile); + + /* Allocate and fill in new structure */ + curFile = bmake_malloc(sizeof *curFile); + + /* + * Once the previous state has been saved, we can get down to reading + * the new file. We set up the name of the file to be the absolute + * name of the include file so error messages refer to the right + * place. + */ + curFile->fname = bmake_strdup(name); + curFile->lineno = line; + curFile->first_lineno = line; + curFile->nextbuf = nextbuf; + curFile->nextbuf_arg = arg; + curFile->lf = NULL; + curFile->depending = doing_depend; /* restore this on EOF */ + + assert(nextbuf != NULL); + + /* Get first block of input data */ + buf = curFile->nextbuf(curFile->nextbuf_arg, &len); + if (buf == NULL) { + /* Was all a waste of time ... */ + if (curFile->fname) + free(curFile->fname); + free(curFile); + return; + } + curFile->P_str = buf; + curFile->P_ptr = buf; + curFile->P_end = buf+len; + + curFile->cond_depth = Cond_save_depth(); + ParseSetParseFile(name); +} + +/*- + *----------------------------------------------------------------------- + * IsInclude -- + * Check if the line is an include directive + * + * Results: + * TRUE if it is. + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +static Boolean +IsInclude(const char *line, Boolean sysv) +{ + static const char inc[] = "include"; + static const size_t inclen = sizeof(inc) - 1; + + // 'd' is not valid for sysv + int o = strchr(&("ds-"[sysv]), *line) != NULL; + + if (strncmp(line + o, inc, inclen) != 0) + return FALSE; + + // Space is not mandatory for BSD .include + return !sysv || isspace((unsigned char)line[inclen + o]); +} + + +#ifdef SYSVINCLUDE +/*- + *----------------------------------------------------------------------- + * IsSysVInclude -- + * Check if the line is a SYSV include directive + * + * Results: + * TRUE if it is. + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +static Boolean +IsSysVInclude(const char *line) +{ + const char *p; + + if (!IsInclude(line, TRUE)) + return FALSE; + + /* Avoid interpeting a dependency line as an include */ + for (p = line; (p = strchr(p, ':')) != NULL;) { + if (*++p == '\0') { + /* end of line -> dependency */ + return FALSE; + } + if (*p == ':' || isspace((unsigned char)*p)) { + /* :: operator or ': ' -> dependency */ + return FALSE; + } + } + return TRUE; +} + +/*- + *--------------------------------------------------------------------- + * ParseTraditionalInclude -- + * Push to another file. + * + * The input is the current line. The file name(s) are + * following the "include". + * + * Results: + * None + * + * Side Effects: + * A structure is added to the includes Lst and readProc, lineno, + * fname and curFILE are altered for the new file + *--------------------------------------------------------------------- + */ +static void +ParseTraditionalInclude(char *line) +{ + char *cp; /* current position in file spec */ + int done = 0; + int silent = (line[0] != 'i') ? 1 : 0; + char *file = &line[silent + 7]; + char *all_files; + + if (DEBUG(PARSE)) { + fprintf(debug_file, "%s: %s\n", __func__, file); + } + + /* + * Skip over whitespace + */ + while (isspace((unsigned char)*file)) + file++; + + /* + * Substitute for any variables in the file name before trying to + * find the thing. + */ + all_files = Var_Subst(NULL, file, VAR_CMD, VARF_WANTRES); + + if (*file == '\0') { + Parse_Error(PARSE_FATAL, + "Filename missing from \"include\""); + goto out; + } + + for (file = all_files; !done; file = cp + 1) { + /* Skip to end of line or next whitespace */ + for (cp = file; *cp && !isspace((unsigned char) *cp); cp++) + continue; + + if (*cp) + *cp = '\0'; + else + done = 1; + + Parse_include_file(file, FALSE, FALSE, silent); + } +out: + free(all_files); +} +#endif + +#ifdef GMAKEEXPORT +/*- + *--------------------------------------------------------------------- + * ParseGmakeExport -- + * Parse export = + * + * And set the environment with it. + * + * Results: + * None + * + * Side Effects: + * None + *--------------------------------------------------------------------- + */ +static void +ParseGmakeExport(char *line) +{ + char *variable = &line[6]; + char *value; + + if (DEBUG(PARSE)) { + fprintf(debug_file, "%s: %s\n", __func__, variable); + } + + /* + * Skip over whitespace + */ + while (isspace((unsigned char)*variable)) + variable++; + + for (value = variable; *value && *value != '='; value++) + continue; + + if (*value != '=') { + Parse_Error(PARSE_FATAL, + "Variable/Value missing from \"export\""); + return; + } + *value++ = '\0'; /* terminate variable */ + + /* + * Expand the value before putting it in the environment. + */ + value = Var_Subst(NULL, value, VAR_CMD, VARF_WANTRES); + setenv(variable, value, 1); + free(value); +} +#endif + +/*- + *--------------------------------------------------------------------- + * ParseEOF -- + * Called when EOF is reached in the current file. If we were reading + * an include file, the includes stack is popped and things set up + * to go back to reading the previous file at the previous location. + * + * Results: + * CONTINUE if there's more to do. DONE if not. + * + * Side Effects: + * The old curFILE, is closed. The includes list is shortened. + * lineno, curFILE, and fname are changed if CONTINUE is returned. + *--------------------------------------------------------------------- + */ +static int +ParseEOF(void) +{ + char *ptr; + size_t len; + + assert(curFile->nextbuf != NULL); + + doing_depend = curFile->depending; /* restore this */ + /* get next input buffer, if any */ + ptr = curFile->nextbuf(curFile->nextbuf_arg, &len); + curFile->P_ptr = ptr; + curFile->P_str = ptr; + curFile->P_end = ptr + len; + curFile->lineno = curFile->first_lineno; + if (ptr != NULL) { + /* Iterate again */ + return CONTINUE; + } + + /* Ensure the makefile (or loop) didn't have mismatched conditionals */ + Cond_restore_depth(curFile->cond_depth); + + if (curFile->lf != NULL) { + loadedfile_destroy(curFile->lf); + curFile->lf = NULL; + } + + /* Dispose of curFile info */ + /* Leak curFile->fname because all the gnodes have pointers to it */ + free(curFile->P_str); + free(curFile); + + curFile = Lst_DeQueue(includes); + + if (curFile == NULL) { + /* We've run out of input */ + Var_Delete(".PARSEDIR", VAR_GLOBAL); + Var_Delete(".PARSEFILE", VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMDIR", VAR_GLOBAL); + Var_Delete(".INCLUDEDFROMFILE", VAR_GLOBAL); + return DONE; + } + + if (DEBUG(PARSE)) + fprintf(debug_file, "ParseEOF: returning to file %s, line %d\n", + curFile->fname, curFile->lineno); + + /* Restore the PARSEDIR/PARSEFILE variables */ + ParseSetParseFile(curFile->fname); + return (CONTINUE); +} + +#define PARSE_RAW 1 +#define PARSE_SKIP 2 + +static char * +ParseGetLine(int flags, int *length) +{ + IFile *cf = curFile; + char *ptr; + char ch; + char *line; + char *line_end; + char *escaped; + char *comment; + char *tp; + + /* Loop through blank lines and comment lines */ + for (;;) { + cf->lineno++; + line = cf->P_ptr; + ptr = line; + line_end = line; + escaped = NULL; + comment = NULL; + for (;;) { + if (cf->P_end != NULL && ptr == cf->P_end) { + /* end of buffer */ + ch = 0; + break; + } + ch = *ptr; + if (ch == 0 || (ch == '\\' && ptr[1] == 0)) { + if (cf->P_end == NULL) + /* End of string (aka for loop) data */ + break; + /* see if there is more we can parse */ + while (ptr++ < cf->P_end) { + if ((ch = *ptr) == '\n') { + if (ptr > line && ptr[-1] == '\\') + continue; + Parse_Error(PARSE_WARNING, + "Zero byte read from file, skipping rest of line."); + break; + } + } + if (cf->nextbuf != NULL) { + /* + * End of this buffer; return EOF and outer logic + * will get the next one. (eww) + */ + break; + } + Parse_Error(PARSE_FATAL, "Zero byte read from file"); + return NULL; + } + + if (ch == '\\') { + /* Don't treat next character as special, remember first one */ + if (escaped == NULL) + escaped = ptr; + if (ptr[1] == '\n') + cf->lineno++; + ptr += 2; + line_end = ptr; + continue; + } + if (ch == '#' && comment == NULL) { + /* Remember first '#' for comment stripping */ + /* Unless previous char was '[', as in modifier :[#] */ + if (!(ptr > line && ptr[-1] == '[')) + comment = line_end; + } + ptr++; + if (ch == '\n') + break; + if (!isspace((unsigned char)ch)) + /* We are not interested in trailing whitespace */ + line_end = ptr; + } + + /* Save next 'to be processed' location */ + cf->P_ptr = ptr; + + /* Check we have a non-comment, non-blank line */ + if (line_end == line || comment == line) { + if (ch == 0) + /* At end of file */ + return NULL; + /* Parse another line */ + continue; + } + + /* We now have a line of data */ + *line_end = 0; + + if (flags & PARSE_RAW) { + /* Leave '\' (etc) in line buffer (eg 'for' lines) */ + *length = line_end - line; + return line; + } + + if (flags & PARSE_SKIP) { + /* Completely ignore non-directives */ + if (line[0] != '.') + continue; + /* We could do more of the .else/.elif/.endif checks here */ + } + break; + } + + /* Brutally ignore anything after a non-escaped '#' in non-commands */ + if (comment != NULL && line[0] != '\t') { + line_end = comment; + *line_end = 0; + } + + /* If we didn't see a '\\' then the in-situ data is fine */ + if (escaped == NULL) { + *length = line_end - line; + return line; + } + + /* Remove escapes from '\n' and '#' */ + tp = ptr = escaped; + escaped = line; + for (; ; *tp++ = ch) { + ch = *ptr++; + if (ch != '\\') { + if (ch == 0) + break; + continue; + } + + ch = *ptr++; + if (ch == 0) { + /* Delete '\\' at end of buffer */ + tp--; + break; + } + + if (ch == '#' && line[0] != '\t') + /* Delete '\\' from before '#' on non-command lines */ + continue; + + if (ch != '\n') { + /* Leave '\\' in buffer for later */ + *tp++ = '\\'; + /* Make sure we don't delete an escaped ' ' from the line end */ + escaped = tp + 1; + continue; + } + + /* Escaped '\n' replace following whitespace with a single ' ' */ + while (ptr[0] == ' ' || ptr[0] == '\t') + ptr++; + ch = ' '; + } + + /* Delete any trailing spaces - eg from empty continuations */ + while (tp > escaped && isspace((unsigned char)tp[-1])) + tp--; + + *tp = 0; + *length = tp - line; + return line; +} + +/*- + *--------------------------------------------------------------------- + * ParseReadLine -- + * Read an entire line from the input file. Called only by Parse_File. + * + * Results: + * A line w/o its newline + * + * Side Effects: + * Only those associated with reading a character + *--------------------------------------------------------------------- + */ +static char * +ParseReadLine(void) +{ + char *line; /* Result */ + int lineLength; /* Length of result */ + int lineno; /* Saved line # */ + int rval; + + for (;;) { + line = ParseGetLine(0, &lineLength); + if (line == NULL) + return NULL; + + if (line[0] != '.') + return line; + + /* + * The line might be a conditional. Ask the conditional module + * about it and act accordingly + */ + switch (Cond_Eval(line)) { + case COND_SKIP: + /* Skip to next conditional that evaluates to COND_PARSE. */ + do { + line = ParseGetLine(PARSE_SKIP, &lineLength); + } while (line && Cond_Eval(line) != COND_PARSE); + if (line == NULL) + break; + continue; + case COND_PARSE: + continue; + case COND_INVALID: /* Not a conditional line */ + /* Check for .for loops */ + rval = For_Eval(line); + if (rval == 0) + /* Not a .for line */ + break; + if (rval < 0) + /* Syntax error - error printed, ignore line */ + continue; + /* Start of a .for loop */ + lineno = curFile->lineno; + /* Accumulate loop lines until matching .endfor */ + do { + line = ParseGetLine(PARSE_RAW, &lineLength); + if (line == NULL) { + Parse_Error(PARSE_FATAL, + "Unexpected end of file in for loop."); + break; + } + } while (For_Accum(line)); + /* Stash each iteration as a new 'input file' */ + For_Run(lineno); + /* Read next line from for-loop buffer */ + continue; + } + return (line); + } +} + +/*- + *----------------------------------------------------------------------- + * ParseFinishLine -- + * Handle the end of a dependency group. + * + * Results: + * Nothing. + * + * Side Effects: + * inLine set FALSE. 'targets' list destroyed. + * + *----------------------------------------------------------------------- + */ +static void +ParseFinishLine(void) +{ + if (inLine) { + Lst_ForEach(targets, Suff_EndTransform, NULL); + Lst_Destroy(targets, ParseHasCommands); + targets = NULL; + inLine = FALSE; + } +} + + +/*- + *--------------------------------------------------------------------- + * Parse_File -- + * Parse a file into its component parts, incorporating it into the + * current dependency graph. This is the main function and controls + * almost every other function in this module + * + * Input: + * name the name of the file being read + * fd Open file to makefile to parse + * + * Results: + * None + * + * Side Effects: + * closes fd. + * Loads. Nodes are added to the list of all targets, nodes and links + * are added to the dependency graph. etc. etc. etc. + *--------------------------------------------------------------------- + */ +void +Parse_File(const char *name, int fd) +{ + char *cp; /* pointer into the line */ + char *line; /* the line we're working on */ + struct loadedfile *lf; + + lf = loadfile(name, fd); + + inLine = FALSE; + fatals = 0; + + if (name == NULL) { + name = "(stdin)"; + } + + Parse_SetInput(name, 0, -1, loadedfile_nextbuf, lf); + curFile->lf = lf; + + do { + for (; (line = ParseReadLine()) != NULL; ) { + if (DEBUG(PARSE)) + fprintf(debug_file, "ParseReadLine (%d): '%s'\n", + curFile->lineno, line); + if (*line == '.') { + /* + * Lines that begin with the special character may be + * include or undef directives. + * On the other hand they can be suffix rules (.c.o: ...) + * or just dependencies for filenames that start '.'. + */ + for (cp = line + 1; isspace((unsigned char)*cp); cp++) { + continue; + } + if (IsInclude(cp, FALSE)) { + ParseDoInclude(cp); + continue; + } + if (strncmp(cp, "undef", 5) == 0) { + char *cp2; + for (cp += 5; isspace((unsigned char) *cp); cp++) + continue; + for (cp2 = cp; !isspace((unsigned char) *cp2) && + (*cp2 != '\0'); cp2++) + continue; + *cp2 = '\0'; + Var_Delete(cp, VAR_GLOBAL); + continue; + } else if (strncmp(cp, "export", 6) == 0) { + for (cp += 6; isspace((unsigned char) *cp); cp++) + continue; + Var_Export(cp, 1); + continue; + } else if (strncmp(cp, "unexport", 8) == 0) { + Var_UnExport(cp); + continue; + } else if (strncmp(cp, "info", 4) == 0 || + strncmp(cp, "error", 5) == 0 || + strncmp(cp, "warning", 7) == 0) { + if (ParseMessage(cp)) + continue; + } + } + + if (*line == '\t') { + /* + * If a line starts with a tab, it can only hope to be + * a creation command. + */ + cp = line + 1; + shellCommand: + for (; isspace ((unsigned char)*cp); cp++) { + continue; + } + if (*cp) { + if (!inLine) + Parse_Error(PARSE_FATAL, + "Unassociated shell command \"%s\"", + cp); + /* + * So long as it's not a blank line and we're actually + * in a dependency spec, add the command to the list of + * commands of all targets in the dependency spec + */ + if (targets) { + cp = bmake_strdup(cp); + Lst_ForEach(targets, ParseAddCmd, cp); +#ifdef CLEANUP + Lst_AtEnd(targCmds, cp); +#endif + } + } + continue; + } + +#ifdef SYSVINCLUDE + if (IsSysVInclude(line)) { + /* + * It's an S3/S5-style "include". + */ + ParseTraditionalInclude(line); + continue; + } +#endif +#ifdef GMAKEEXPORT + if (strncmp(line, "export", 6) == 0 && + isspace((unsigned char) line[6]) && + strchr(line, ':') == NULL) { + /* + * It's a Gmake "export". + */ + ParseGmakeExport(line); + continue; + } +#endif + if (Parse_IsVar(line)) { + ParseFinishLine(); + Parse_DoVar(line, VAR_GLOBAL); + continue; + } + +#ifndef POSIX + /* + * To make life easier on novices, if the line is indented we + * first make sure the line has a dependency operator in it. + * If it doesn't have an operator and we're in a dependency + * line's script, we assume it's actually a shell command + * and add it to the current list of targets. + */ + cp = line; + if (isspace((unsigned char) line[0])) { + while ((*cp != '\0') && isspace((unsigned char) *cp)) + cp++; + while (*cp && (ParseIsEscaped(line, cp) || + (*cp != ':') && (*cp != '!'))) { + cp++; + } + if (*cp == '\0') { + if (inLine) { + Parse_Error(PARSE_WARNING, + "Shell command needs a leading tab"); + goto shellCommand; + } + } + } +#endif + ParseFinishLine(); + + /* + * For some reason - probably to make the parser impossible - + * a ';' can be used to separate commands from dependencies. + * Attempt to avoid ';' inside substitution patterns. + */ + { + int level = 0; + + for (cp = line; *cp != 0; cp++) { + if (*cp == '\\' && cp[1] != 0) { + cp++; + continue; + } + if (*cp == '$' && + (cp[1] == '(' || cp[1] == '{')) { + level++; + continue; + } + if (level > 0) { + if (*cp == ')' || *cp == '}') { + level--; + continue; + } + } else if (*cp == ';') { + break; + } + } + } + if (*cp != 0) + /* Terminate the dependency list at the ';' */ + *cp++ = 0; + else + cp = NULL; + + /* + * We now know it's a dependency line so it needs to have all + * variables expanded before being parsed. Tell the variable + * module to complain if some variable is undefined... + */ + line = Var_Subst(NULL, line, VAR_CMD, VARF_UNDEFERR|VARF_WANTRES); + + /* + * Need a non-circular list for the target nodes + */ + if (targets) + Lst_Destroy(targets, NULL); + + targets = Lst_Init(FALSE); + inLine = TRUE; + + ParseDoDependency(line); + free(line); + + /* If there were commands after a ';', add them now */ + if (cp != NULL) { + goto shellCommand; + } + } + /* + * Reached EOF, but it may be just EOF of an include file... + */ + } while (ParseEOF() == CONTINUE); + + if (fatals) { + (void)fflush(stdout); + (void)fprintf(stderr, + "%s: Fatal errors encountered -- cannot continue", + progname); + PrintOnError(NULL, NULL); + exit(1); + } +} + +/*- + *--------------------------------------------------------------------- + * Parse_Init -- + * initialize the parsing module + * + * Results: + * none + * + * Side Effects: + * the parseIncPath list is initialized... + *--------------------------------------------------------------------- + */ +void +Parse_Init(void) +{ + mainNode = NULL; + parseIncPath = Lst_Init(FALSE); + sysIncPath = Lst_Init(FALSE); + defIncPath = Lst_Init(FALSE); + includes = Lst_Init(FALSE); +#ifdef CLEANUP + targCmds = Lst_Init(FALSE); +#endif +} + +void +Parse_End(void) +{ +#ifdef CLEANUP + Lst_Destroy(targCmds, (FreeProc *)free); + if (targets) + Lst_Destroy(targets, NULL); + Lst_Destroy(defIncPath, Dir_Destroy); + Lst_Destroy(sysIncPath, Dir_Destroy); + Lst_Destroy(parseIncPath, Dir_Destroy); + Lst_Destroy(includes, NULL); /* Should be empty now */ +#endif +} + + +/*- + *----------------------------------------------------------------------- + * Parse_MainName -- + * Return a Lst of the main target to create for main()'s sake. If + * no such target exists, we Punt with an obnoxious error message. + * + * Results: + * A Lst of the single node to create. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Lst +Parse_MainName(void) +{ + Lst mainList; /* result list */ + + mainList = Lst_Init(FALSE); + + if (mainNode == NULL) { + Punt("no target to make."); + /*NOTREACHED*/ + } else if (mainNode->type & OP_DOUBLEDEP) { + (void)Lst_AtEnd(mainList, mainNode); + Lst_Concat(mainList, mainNode->cohorts, LST_CONCNEW); + } + else + (void)Lst_AtEnd(mainList, mainNode); + Var_Append(".TARGETS", mainNode->name, VAR_GLOBAL); + return (mainList); +} + +/*- + *----------------------------------------------------------------------- + * ParseMark -- + * Add the filename and lineno to the GNode so that we remember + * where it was first defined. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static void +ParseMark(GNode *gn) +{ + gn->fname = curFile->fname; + gn->lineno = curFile->lineno; +} diff --git a/usr.bin/make/pathnames.h b/usr.bin/make/pathnames.h new file mode 100644 index 0000000..12c4f3d --- /dev/null +++ b/usr.bin/make/pathnames.h @@ -0,0 +1,53 @@ +/* $NetBSD: pathnames.h,v 1.17 2009/04/11 09:41:18 apb Exp $ */ + +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pathnames.h 5.2 (Berkeley) 6/1/90 + */ + +#ifndef MAKE_NATIVE +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif +#else +#include +#endif + +#define _PATH_OBJDIR "obj" +#define _PATH_OBJDIRPREFIX "/usr/obj" +#ifndef _PATH_DEFSHELLDIR +#define _PATH_DEFSHELLDIR "/bin" +#endif +#define _PATH_DEFSYSMK "sys.mk" +#ifndef _PATH_DEFSYSPATH +#define _PATH_DEFSYSPATH "/usr/share/mk" +#endif +#ifndef _PATH_TMP +#define _PATH_TMP "/tmp/" /* with trailing slash */ +#endif diff --git a/usr.bin/make/sprite.h b/usr.bin/make/sprite.h new file mode 100644 index 0000000..cdcffd9 --- /dev/null +++ b/usr.bin/make/sprite.h @@ -0,0 +1,116 @@ +/* $NetBSD: sprite.h,v 1.14 2017/05/31 22:02:06 maya Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)sprite.h 8.1 (Berkeley) 6/6/93 + */ + +/* + * sprite.h -- + * + * Common constants and type declarations for Sprite. + */ + +#ifndef MAKE_SPRITE_H +#define MAKE_SPRITE_H + + +/* + * A boolean type is defined as an integer, not an enum. This allows a + * boolean argument to be an expression that isn't strictly 0 or 1 valued. + */ + +typedef int Boolean; +#ifndef TRUE +#define TRUE 1 +#endif /* TRUE */ +#ifndef FALSE +#define FALSE 0 +#endif /* FALSE */ + +/* + * Functions that must return a status can return a ReturnStatus to + * indicate success or type of failure. + */ + +typedef int ReturnStatus; + +/* + * The following statuses overlap with the first 2 generic statuses + * defined in status.h: + * + * SUCCESS There was no error. + * FAILURE There was a general error. + */ + +#define SUCCESS 0x00000000 +#define FAILURE 0x00000001 + +#endif /* MAKE_SPRITE_H */ diff --git a/usr.bin/make/str.c b/usr.bin/make/str.c new file mode 100644 index 0000000..b5255bc --- /dev/null +++ b/usr.bin/make/str.c @@ -0,0 +1,526 @@ +/* $NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $ */ + +/*- + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)str.c 5.8 (Berkeley) 6/1/90"; +#else +__RCSID("$NetBSD: str.c,v 1.38 2017/04/21 22:15:44 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +#include "make.h" + +/*- + * str_concat -- + * concatenate the two strings, inserting a space or slash between them, + * freeing them if requested. + * + * returns -- + * the resulting string in allocated space. + */ +char * +str_concat(const char *s1, const char *s2, int flags) +{ + int len1, len2; + char *result; + + /* get the length of both strings */ + len1 = strlen(s1); + len2 = strlen(s2); + + /* allocate length plus separator plus EOS */ + result = bmake_malloc((unsigned int)(len1 + len2 + 2)); + + /* copy first string into place */ + memcpy(result, s1, len1); + + /* add separator character */ + if (flags & STR_ADDSPACE) { + result[len1] = ' '; + ++len1; + } else if (flags & STR_ADDSLASH) { + result[len1] = '/'; + ++len1; + } + + /* copy second string plus EOS into place */ + memcpy(result + len1, s2, len2 + 1); + + return(result); +} + +/*- + * brk_string -- + * Fracture a string into an array of words (as delineated by tabs or + * spaces) taking quotation marks into account. Leading tabs/spaces + * are ignored. + * + * If expand is TRUE, quotes are removed and escape sequences + * such as \r, \t, etc... are expanded. + * + * returns -- + * Pointer to the array of pointers to the words. + * Memory containing the actual words in *buffer. + * Both of these must be free'd by the caller. + * Number of words in *store_argc. + */ +char ** +brk_string(const char *str, int *store_argc, Boolean expand, char **buffer) +{ + int argc, ch; + char inquote, *start, *t; + const char *p; + int len; + int argmax = 50, curlen = 0; + char **argv; + + /* skip leading space chars. */ + for (; *str == ' ' || *str == '\t'; ++str) + continue; + + /* allocate room for a copy of the string */ + if ((len = strlen(str) + 1) > curlen) + *buffer = bmake_malloc(curlen = len); + + /* + * initial argmax based on len + */ + argmax = MAX((len / 5), 50); + argv = bmake_malloc((argmax + 1) * sizeof(char *)); + + /* + * copy the string; at the same time, parse backslashes, + * quotes and build the argument list. + */ + argc = 0; + inquote = '\0'; + for (p = str, start = t = *buffer;; ++p) { + switch(ch = *p) { + case '"': + case '\'': + if (inquote) { + if (inquote == ch) + inquote = '\0'; + else + break; + } + else { + inquote = (char) ch; + /* Don't miss "" or '' */ + if (start == NULL && p[1] == inquote) { + if (!expand) { + start = t; + *t++ = ch; + } else + start = t + 1; + p++; + inquote = '\0'; + break; + } + } + if (!expand) { + if (!start) + start = t; + *t++ = ch; + } + continue; + case ' ': + case '\t': + case '\n': + if (inquote) + break; + if (!start) + continue; + /* FALLTHROUGH */ + case '\0': + /* + * end of a token -- make sure there's enough argv + * space and save off a pointer. + */ + if (!start) + goto done; + + *t++ = '\0'; + if (argc == argmax) { + argmax *= 2; /* ramp up fast */ + argv = (char **)bmake_realloc(argv, + (argmax + 1) * sizeof(char *)); + } + argv[argc++] = start; + start = NULL; + if (ch == '\n' || ch == '\0') { + if (expand && inquote) { + free(argv); + free(*buffer); + *buffer = NULL; + return NULL; + } + goto done; + } + continue; + case '\\': + if (!expand) { + if (!start) + start = t; + *t++ = '\\'; + if (*(p+1) == '\0') /* catch '\' at end of line */ + continue; + ch = *++p; + break; + } + + switch (ch = *++p) { + case '\0': + case '\n': + /* hmmm; fix it up as best we can */ + ch = '\\'; + --p; + break; + case 'b': + ch = '\b'; + break; + case 'f': + ch = '\f'; + break; + case 'n': + ch = '\n'; + break; + case 'r': + ch = '\r'; + break; + case 't': + ch = '\t'; + break; + } + break; + } + if (!start) + start = t; + *t++ = (char) ch; + } +done: argv[argc] = NULL; + *store_argc = argc; + return(argv); +} + +/* + * Str_FindSubstring -- See if a string contains a particular substring. + * + * Input: + * string String to search. + * substring Substring to find in string. + * + * Results: If string contains substring, the return value is the location of + * the first matching instance of substring in string. If string doesn't + * contain substring, the return value is NULL. Matching is done on an exact + * character-for-character basis with no wildcards or special characters. + * + * Side effects: None. + */ +char * +Str_FindSubstring(const char *string, const char *substring) +{ + const char *a, *b; + + /* + * First scan quickly through the two strings looking for a single- + * character match. When it's found, then compare the rest of the + * substring. + */ + + for (b = substring; *string != 0; string += 1) { + if (*string != *b) + continue; + a = string; + for (;;) { + if (*b == 0) + return UNCONST(string); + if (*a++ != *b++) + break; + } + b = substring; + } + return NULL; +} + +/* + * Str_Match -- + * + * See if a particular string matches a particular pattern. + * + * Results: Non-zero is returned if string matches pattern, 0 otherwise. The + * matching operation permits the following special characters in the + * pattern: *?\[] (see the man page for details on what these mean). + * + * XXX this function does not detect or report malformed patterns. + * + * Side effects: None. + */ +int +Str_Match(const char *string, const char *pattern) +{ + char c2; + + for (;;) { + /* + * See if we're at the end of both the pattern and the + * string. If, we succeeded. If we're at the end of the + * pattern but not at the end of the string, we failed. + */ + if (*pattern == 0) + return(!*string); + if (*string == 0 && *pattern != '*') + return(0); + /* + * Check for a "*" as the next pattern character. It matches + * any substring. We handle this by calling ourselves + * recursively for each postfix of string, until either we + * match or we reach the end of the string. + */ + if (*pattern == '*') { + pattern += 1; + if (*pattern == 0) + return(1); + while (*string != 0) { + if (Str_Match(string, pattern)) + return(1); + ++string; + } + return(0); + } + /* + * Check for a "?" as the next pattern character. It matches + * any single character. + */ + if (*pattern == '?') + goto thisCharOK; + /* + * Check for a "[" as the next pattern character. It is + * followed by a list of characters that are acceptable, or + * by a range (two characters separated by "-"). + */ + if (*pattern == '[') { + int nomatch; + + ++pattern; + if (*pattern == '^') { + ++pattern; + nomatch = 1; + } else + nomatch = 0; + for (;;) { + if ((*pattern == ']') || (*pattern == 0)) { + if (nomatch) + break; + return(0); + } + if (*pattern == *string) + break; + if (pattern[1] == '-') { + c2 = pattern[2]; + if (c2 == 0) + return(nomatch); + if ((*pattern <= *string) && + (c2 >= *string)) + break; + if ((*pattern >= *string) && + (c2 <= *string)) + break; + pattern += 2; + } + ++pattern; + } + if (nomatch && (*pattern != ']') && (*pattern != 0)) + return 0; + while ((*pattern != ']') && (*pattern != 0)) + ++pattern; + goto thisCharOK; + } + /* + * If the next pattern character is '/', just strip off the + * '/' so we do exact matching on the character that follows. + */ + if (*pattern == '\\') { + ++pattern; + if (*pattern == 0) + return(0); + } + /* + * There's no special character. Just make sure that the + * next characters of each string match. + */ + if (*pattern != *string) + return(0); +thisCharOK: ++pattern; + ++string; + } +} + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVMatch -- + * Check word against pattern for a match (% is wild), + * + * Input: + * word Word to examine + * pattern Pattern to examine against + * len Number of characters to substitute + * + * Results: + * Returns the beginning position of a match or null. The number + * of characters matched is returned in len. + * + * Side Effects: + * None + * + *----------------------------------------------------------------------- + */ +char * +Str_SYSVMatch(const char *word, const char *pattern, int *len) +{ + const char *p = pattern; + const char *w = word; + const char *m; + + if (*p == '\0') { + /* Null pattern is the whole string */ + *len = strlen(w); + return UNCONST(w); + } + + if ((m = strchr(p, '%')) != NULL) { + /* check that the prefix matches */ + for (; p != m && *w && *w == *p; w++, p++) + continue; + + if (p != m) + return NULL; /* No match */ + + if (*++p == '\0') { + /* No more pattern, return the rest of the string */ + *len = strlen(w); + return UNCONST(w); + } + } + + m = w; + + /* Find a matching tail */ + do + if (strcmp(p, w) == 0) { + *len = w - m; + return UNCONST(m); + } + while (*w++ != '\0'); + + return NULL; +} + + +/*- + *----------------------------------------------------------------------- + * Str_SYSVSubst -- + * Substitute '%' on the pattern with len characters from src. + * If the pattern does not contain a '%' prepend len characters + * from src. + * + * Results: + * None + * + * Side Effects: + * Places result on buf + * + *----------------------------------------------------------------------- + */ +void +Str_SYSVSubst(Buffer *buf, char *pat, char *src, int len) +{ + char *m; + + if ((m = strchr(pat, '%')) != NULL) { + /* Copy the prefix */ + Buf_AddBytes(buf, m - pat, pat); + /* skip the % */ + pat = m + 1; + } + + /* Copy the pattern */ + Buf_AddBytes(buf, len, src); + + /* append the rest */ + Buf_AddBytes(buf, strlen(pat), pat); +} diff --git a/usr.bin/make/strlist.c b/usr.bin/make/strlist.c new file mode 100644 index 0000000..3fb2f7d --- /dev/null +++ b/usr.bin/make/strlist.c @@ -0,0 +1,93 @@ +/* $NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $ */ + +/*- + * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by David Laight. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $"; +#else +#include +#ifndef lint +__RCSID("$NetBSD: strlist.c,v 1.4 2009/01/24 11:59:39 dsl Exp $"); +#endif /* not lint */ +#endif + +#include +#include +#include "strlist.h" +#include "make_malloc.h" + +void +strlist_init(strlist_t *sl) +{ + sl->sl_num = 0; + sl->sl_max = 0; + sl->sl_items = NULL; +} + +void +strlist_clean(strlist_t *sl) +{ + char *str; + int i; + + STRLIST_FOREACH(str, sl, i) + free(str); + free(sl->sl_items); + + sl->sl_num = 0; + sl->sl_max = 0; + sl->sl_items = NULL; +} + +void +strlist_add_str(strlist_t *sl, char *str, unsigned int info) +{ + unsigned int n; + strlist_item_t *items; + + if (str == NULL) + return; + + n = sl->sl_num + 1; + sl->sl_num = n; + items = sl->sl_items; + if (n >= sl->sl_max) { + items = bmake_realloc(items, (n + 7) * sizeof *sl->sl_items); + sl->sl_items = items; + sl->sl_max = n + 6; + } + items += n - 1; + items->si_str = str; + items->si_info = info; + items[1].si_str = NULL; /* STRLIST_FOREACH() terminator */ +} diff --git a/usr.bin/make/strlist.h b/usr.bin/make/strlist.h new file mode 100644 index 0000000..2fc049e --- /dev/null +++ b/usr.bin/make/strlist.h @@ -0,0 +1,62 @@ +/* $NetBSD: strlist.h,v 1.3 2009/01/16 21:15:34 dsl Exp $ */ + +/*- + * Copyright (c) 2008 - 2009 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by David Laight. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _STRLIST_H +#define _STRLIST_H + +typedef struct { + char *si_str; + unsigned int si_info; +} strlist_item_t; + +typedef struct { + unsigned int sl_num; + unsigned int sl_max; + strlist_item_t *sl_items; +} strlist_t; + +void strlist_init(strlist_t *); +void strlist_clean(strlist_t *); +void strlist_add_str(strlist_t *, char *, unsigned int); + +#define strlist_num(sl) ((sl)->sl_num) +#define strlist_str(sl, n) ((sl)->sl_items[n].si_str) +#define strlist_info(sl, n) ((sl)->sl_items[n].si_info) +#define strlist_set_info(sl, n, v) ((void)((sl)->sl_items[n].si_info = (v))) + +#define STRLIST_FOREACH(v, sl, index) \ + if ((sl)->sl_items != NULL) \ + for (index = 0; (v = strlist_str(sl, index)) != NULL; index++) + +#endif /* _STRLIST_H */ diff --git a/usr.bin/make/suff.c b/usr.bin/make/suff.c new file mode 100644 index 0000000..df0306a --- /dev/null +++ b/usr.bin/make/suff.c @@ -0,0 +1,2676 @@ +/* $NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)suff.c 8.4 (Berkeley) 3/21/94"; +#else +__RCSID("$NetBSD: suff.c,v 1.86 2017/04/16 20:38:18 riastradh Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * suff.c -- + * Functions to maintain suffix lists and find implicit dependents + * using suffix transformation rules + * + * Interface: + * Suff_Init Initialize all things to do with suffixes. + * + * Suff_End Cleanup the module + * + * Suff_DoPaths This function is used to make life easier + * when searching for a file according to its + * suffix. It takes the global search path, + * as defined using the .PATH: target, and appends + * its directories to the path of each of the + * defined suffixes, as specified using + * .PATH: targets. In addition, all + * directories given for suffixes labeled as + * include files or libraries, using the .INCLUDES + * or .LIBS targets, are played with using + * Dir_MakeFlags to create the .INCLUDES and + * .LIBS global variables. + * + * Suff_ClearSuffixes Clear out all the suffixes and defined + * transformations. + * + * Suff_IsTransform Return TRUE if the passed string is the lhs + * of a transformation rule. + * + * Suff_AddSuffix Add the passed string as another known suffix. + * + * Suff_GetPath Return the search path for the given suffix. + * + * Suff_AddInclude Mark the given suffix as denoting an include + * file. + * + * Suff_AddLib Mark the given suffix as denoting a library. + * + * Suff_AddTransform Add another transformation to the suffix + * graph. Returns GNode suitable for framing, I + * mean, tacking commands, attributes, etc. on. + * + * Suff_SetNull Define the suffix to consider the suffix of + * any file that doesn't have a known one. + * + * Suff_FindDeps Find implicit sources for and the location of + * a target based on its suffix. Returns the + * bottom-most node added to the graph or NULL + * if the target had no implicit sources. + * + * Suff_FindPath Return the appropriate path to search in + * order to find the node. + */ + +#include +#include +#include "make.h" +#include "hash.h" +#include "dir.h" + +static Lst sufflist; /* Lst of suffixes */ +#ifdef CLEANUP +static Lst suffClean; /* Lst of suffixes to be cleaned */ +#endif +static Lst srclist; /* Lst of sources */ +static Lst transforms; /* Lst of transformation rules */ + +static int sNum = 0; /* Counter for assigning suffix numbers */ + +/* + * Structure describing an individual suffix. + */ +typedef struct _Suff { + char *name; /* The suffix itself */ + int nameLen; /* Length of the suffix */ + short flags; /* Type of suffix */ +#define SUFF_INCLUDE 0x01 /* One which is #include'd */ +#define SUFF_LIBRARY 0x02 /* One which contains a library */ +#define SUFF_NULL 0x04 /* The empty suffix */ + Lst searchPath; /* The path along which files of this suffix + * may be found */ + int sNum; /* The suffix number */ + int refCount; /* Reference count of list membership */ + Lst parents; /* Suffixes we have a transformation to */ + Lst children; /* Suffixes we have a transformation from */ + Lst ref; /* List of lists this suffix is referenced */ +} Suff; + +/* + * for SuffSuffIsSuffix + */ +typedef struct { + char *ename; /* The end of the name */ + int len; /* Length of the name */ +} SuffixCmpData; + +/* + * Structure used in the search for implied sources. + */ +typedef struct _Src { + char *file; /* The file to look for */ + char *pref; /* Prefix from which file was formed */ + Suff *suff; /* The suffix on the file */ + struct _Src *parent; /* The Src for which this is a source */ + GNode *node; /* The node describing the file */ + int children; /* Count of existing children (so we don't free + * this thing too early or never nuke it) */ +#ifdef DEBUG_SRC + Lst cp; /* Debug; children list */ +#endif +} Src; + +/* + * A structure for passing more than one argument to the Lst-library-invoked + * function... + */ +typedef struct { + Lst l; + Src *s; +} LstSrc; + +typedef struct { + GNode **gn; + Suff *s; + Boolean r; +} GNodeSuff; + +static Suff *suffNull; /* The NULL suffix for this run */ +static Suff *emptySuff; /* The empty suffix required for POSIX + * single-suffix transformation rules */ + + +static const char *SuffStrIsPrefix(const char *, const char *); +static char *SuffSuffIsSuffix(const Suff *, const SuffixCmpData *); +static int SuffSuffIsSuffixP(const void *, const void *); +static int SuffSuffHasNameP(const void *, const void *); +static int SuffSuffIsPrefix(const void *, const void *); +static int SuffGNHasNameP(const void *, const void *); +static void SuffUnRef(void *, void *); +static void SuffFree(void *); +static void SuffInsert(Lst, Suff *); +static void SuffRemove(Lst, Suff *); +static Boolean SuffParseTransform(char *, Suff **, Suff **); +static int SuffRebuildGraph(void *, void *); +static int SuffScanTargets(void *, void *); +static int SuffAddSrc(void *, void *); +static int SuffRemoveSrc(Lst); +static void SuffAddLevel(Lst, Src *); +static Src *SuffFindThem(Lst, Lst); +static Src *SuffFindCmds(Src *, Lst); +static void SuffExpandChildren(LstNode, GNode *); +static void SuffExpandWildcards(LstNode, GNode *); +static Boolean SuffApplyTransform(GNode *, GNode *, Suff *, Suff *); +static void SuffFindDeps(GNode *, Lst); +static void SuffFindArchiveDeps(GNode *, Lst); +static void SuffFindNormalDeps(GNode *, Lst); +static int SuffPrintName(void *, void *); +static int SuffPrintSuff(void *, void *); +static int SuffPrintTrans(void *, void *); + + /*************** Lst Predicates ****************/ +/*- + *----------------------------------------------------------------------- + * SuffStrIsPrefix -- + * See if pref is a prefix of str. + * + * Input: + * pref possible prefix + * str string to check + * + * Results: + * NULL if it ain't, pointer to character in str after prefix if so + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static const char * +SuffStrIsPrefix(const char *pref, const char *str) +{ + while (*str && *pref == *str) { + pref++; + str++; + } + + return (*pref ? NULL : str); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffix -- + * See if suff is a suffix of str. sd->ename should point to THE END + * of the string to check. (THE END == the null byte) + * + * Input: + * s possible suffix + * sd string to examine + * + * Results: + * NULL if it ain't, pointer to character in str before suffix if + * it is. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static char * +SuffSuffIsSuffix(const Suff *s, const SuffixCmpData *sd) +{ + char *p1; /* Pointer into suffix name */ + char *p2; /* Pointer into string being examined */ + + if (sd->len < s->nameLen) + return NULL; /* this string is shorter than the suffix */ + + p1 = s->name + s->nameLen; + p2 = sd->ename; + + while (p1 >= s->name && *p1 == *p2) { + p1--; + p2--; + } + + return (p1 == s->name - 1 ? p2 : NULL); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsSuffixP -- + * Predicate form of SuffSuffIsSuffix. Passed as the callback function + * to Lst_Find. + * + * Results: + * 0 if the suffix is the one desired, non-zero if not. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static int +SuffSuffIsSuffixP(const void *s, const void *sd) +{ + return(!SuffSuffIsSuffix(s, sd)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffHasNameP -- + * Callback procedure for finding a suffix based on its name. Used by + * Suff_GetPath. + * + * Input: + * s Suffix to check + * sd Desired name + * + * Results: + * 0 if the suffix is of the given name. non-zero otherwise. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffHasNameP(const void *s, const void *sname) +{ + return (strcmp(sname, ((const Suff *)s)->name)); +} + +/*- + *----------------------------------------------------------------------- + * SuffSuffIsPrefix -- + * See if the suffix described by s is a prefix of the string. Care + * must be taken when using this to search for transformations and + * what-not, since there could well be two suffixes, one of which + * is a prefix of the other... + * + * Input: + * s suffix to compare + * str string to examine + * + * Results: + * 0 if s is a prefix of str. non-zero otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffSuffIsPrefix(const void *s, const void *str) +{ + return SuffStrIsPrefix(((const Suff *)s)->name, str) == NULL; +} + +/*- + *----------------------------------------------------------------------- + * SuffGNHasNameP -- + * See if the graph node has the desired name + * + * Input: + * gn current node we're looking at + * name name we're looking for + * + * Results: + * 0 if it does. non-zero if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static int +SuffGNHasNameP(const void *gn, const void *name) +{ + return (strcmp(name, ((const GNode *)gn)->name)); +} + + /*********** Maintenance Functions ************/ + +static void +SuffUnRef(void *lp, void *sp) +{ + Lst l = (Lst) lp; + + LstNode ln = Lst_Member(l, sp); + if (ln != NULL) { + Lst_Remove(l, ln); + ((Suff *)sp)->refCount--; + } +} + +/*- + *----------------------------------------------------------------------- + * SuffFree -- + * Free up all memory associated with the given suffix structure. + * + * Results: + * none + * + * Side Effects: + * the suffix entry is detroyed + *----------------------------------------------------------------------- + */ +static void +SuffFree(void *sp) +{ + Suff *s = (Suff *)sp; + + if (s == suffNull) + suffNull = NULL; + + if (s == emptySuff) + emptySuff = NULL; + +#ifdef notdef + /* We don't delete suffixes in order, so we cannot use this */ + if (s->refCount) + Punt("Internal error deleting suffix `%s' with refcount = %d", s->name, + s->refCount); +#endif + + Lst_Destroy(s->ref, NULL); + Lst_Destroy(s->children, NULL); + Lst_Destroy(s->parents, NULL); + Lst_Destroy(s->searchPath, Dir_Destroy); + + free(s->name); + free(s); +} + +/*- + *----------------------------------------------------------------------- + * SuffRemove -- + * Remove the suffix into the list + * + * Results: + * None + * + * Side Effects: + * The reference count for the suffix is decremented and the + * suffix is possibly freed + *----------------------------------------------------------------------- + */ +static void +SuffRemove(Lst l, Suff *s) +{ + SuffUnRef(l, s); + if (s->refCount == 0) { + SuffUnRef(sufflist, s); + SuffFree(s); + } +} + +/*- + *----------------------------------------------------------------------- + * SuffInsert -- + * Insert the suffix into the list keeping the list ordered by suffix + * numbers. + * + * Input: + * l the list where in s should be inserted + * s the suffix to insert + * + * Results: + * None + * + * Side Effects: + * The reference count of the suffix is incremented + *----------------------------------------------------------------------- + */ +static void +SuffInsert(Lst l, Suff *s) +{ + LstNode ln; /* current element in l we're examining */ + Suff *s2 = NULL; /* the suffix descriptor in this element */ + + if (Lst_Open(l) == FAILURE) { + return; + } + while ((ln = Lst_Next(l)) != NULL) { + s2 = (Suff *)Lst_Datum(ln); + if (s2->sNum >= s->sNum) { + break; + } + } + + Lst_Close(l); + if (DEBUG(SUFF)) { + fprintf(debug_file, "inserting %s(%d)...", s->name, s->sNum); + } + if (ln == NULL) { + if (DEBUG(SUFF)) { + fprintf(debug_file, "at end of list\n"); + } + (void)Lst_AtEnd(l, s); + s->refCount++; + (void)Lst_AtEnd(s->ref, l); + } else if (s2->sNum != s->sNum) { + if (DEBUG(SUFF)) { + fprintf(debug_file, "before %s(%d)\n", s2->name, s2->sNum); + } + (void)Lst_InsertBefore(l, ln, s); + s->refCount++; + (void)Lst_AtEnd(s->ref, l); + } else if (DEBUG(SUFF)) { + fprintf(debug_file, "already there\n"); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_ClearSuffixes -- + * This is gross. Nuke the list of suffixes but keep all transformation + * rules around. The transformation graph is destroyed in this process, + * but we leave the list of rules so when a new graph is formed the rules + * will remain. + * This function is called from the parse module when a + * .SUFFIXES:\n line is encountered. + * + * Results: + * none + * + * Side Effects: + * the sufflist and its graph nodes are destroyed + *----------------------------------------------------------------------- + */ +void +Suff_ClearSuffixes(void) +{ +#ifdef CLEANUP + Lst_Concat(suffClean, sufflist, LST_CONCLINK); +#endif + sufflist = Lst_Init(FALSE); + sNum = 0; + if (suffNull) + SuffFree(suffNull); + emptySuff = suffNull = bmake_malloc(sizeof(Suff)); + + suffNull->name = bmake_strdup(""); + suffNull->nameLen = 0; + suffNull->searchPath = Lst_Init(FALSE); + Dir_Concat(suffNull->searchPath, dirSearchPath); + suffNull->children = Lst_Init(FALSE); + suffNull->parents = Lst_Init(FALSE); + suffNull->ref = Lst_Init(FALSE); + suffNull->sNum = sNum++; + suffNull->flags = SUFF_NULL; + suffNull->refCount = 1; +} + +/*- + *----------------------------------------------------------------------- + * SuffParseTransform -- + * Parse a transformation string to find its two component suffixes. + * + * Input: + * str String being parsed + * srcPtr Place to store source of trans. + * targPtr Place to store target of trans. + * + * Results: + * TRUE if the string is a valid transformation and FALSE otherwise. + * + * Side Effects: + * The passed pointers are overwritten. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffParseTransform(char *str, Suff **srcPtr, Suff **targPtr) +{ + LstNode srcLn; /* element in suffix list of trans source*/ + Suff *src; /* Source of transformation */ + LstNode targLn; /* element in suffix list of trans target*/ + char *str2; /* Extra pointer (maybe target suffix) */ + LstNode singleLn; /* element in suffix list of any suffix + * that exactly matches str */ + Suff *single = NULL;/* Source of possible transformation to + * null suffix */ + + srcLn = NULL; + singleLn = NULL; + + /* + * Loop looking first for a suffix that matches the start of the + * string and then for one that exactly matches the rest of it. If + * we can find two that meet these criteria, we've successfully + * parsed the string. + */ + for (;;) { + if (srcLn == NULL) { + srcLn = Lst_Find(sufflist, str, SuffSuffIsPrefix); + } else { + srcLn = Lst_FindFrom(sufflist, Lst_Succ(srcLn), str, + SuffSuffIsPrefix); + } + if (srcLn == NULL) { + /* + * Ran out of source suffixes -- no such rule + */ + if (singleLn != NULL) { + /* + * Not so fast Mr. Smith! There was a suffix that encompassed + * the entire string, so we assume it was a transformation + * to the null suffix (thank you POSIX). We still prefer to + * find a double rule over a singleton, hence we leave this + * check until the end. + * + * XXX: Use emptySuff over suffNull? + */ + *srcPtr = single; + *targPtr = suffNull; + return(TRUE); + } + return (FALSE); + } + src = (Suff *)Lst_Datum(srcLn); + str2 = str + src->nameLen; + if (*str2 == '\0') { + single = src; + singleLn = srcLn; + } else { + targLn = Lst_Find(sufflist, str2, SuffSuffHasNameP); + if (targLn != NULL) { + *srcPtr = src; + *targPtr = (Suff *)Lst_Datum(targLn); + return (TRUE); + } + } + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_IsTransform -- + * Return TRUE if the given string is a transformation rule + * + * + * Input: + * str string to check + * + * Results: + * TRUE if the string is a concatenation of two known suffixes. + * FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Suff_IsTransform(char *str) +{ + Suff *src, *targ; + + return (SuffParseTransform(str, &src, &targ)); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddTransform -- + * Add the transformation rule described by the line to the + * list of rules and place the transformation itself in the graph + * + * Input: + * line name of transformation to add + * + * Results: + * The node created for the transformation in the transforms list + * + * Side Effects: + * The node is placed on the end of the transforms Lst and links are + * made between the two suffixes mentioned in the target name + *----------------------------------------------------------------------- + */ +GNode * +Suff_AddTransform(char *line) +{ + GNode *gn; /* GNode of transformation rule */ + Suff *s, /* source suffix */ + *t; /* target suffix */ + LstNode ln; /* Node for existing transformation */ + + ln = Lst_Find(transforms, line, SuffGNHasNameP); + if (ln == NULL) { + /* + * Make a new graph node for the transformation. It will be filled in + * by the Parse module. + */ + gn = Targ_NewGN(line); + (void)Lst_AtEnd(transforms, gn); + } else { + /* + * New specification for transformation rule. Just nuke the old list + * of commands so they can be filled in again... We don't actually + * free the commands themselves, because a given command can be + * attached to several different transformations. + */ + gn = (GNode *)Lst_Datum(ln); + Lst_Destroy(gn->commands, NULL); + Lst_Destroy(gn->children, NULL); + gn->commands = Lst_Init(FALSE); + gn->children = Lst_Init(FALSE); + } + + gn->type = OP_TRANSFORM; + + (void)SuffParseTransform(line, &s, &t); + + /* + * link the two together in the proper relationship and order + */ + if (DEBUG(SUFF)) { + fprintf(debug_file, "defining transformation from `%s' to `%s'\n", + s->name, t->name); + } + SuffInsert(t->children, s); + SuffInsert(s->parents, t); + + return (gn); +} + +/*- + *----------------------------------------------------------------------- + * Suff_EndTransform -- + * Handle the finish of a transformation definition, removing the + * transformation from the graph if it has neither commands nor + * sources. This is a callback procedure for the Parse module via + * Lst_ForEach + * + * Input: + * gnp Node for transformation + * dummy Node for transformation + * + * Results: + * === 0 + * + * Side Effects: + * If the node has no commands or children, the children and parents + * lists of the affected suffixes are altered. + * + *----------------------------------------------------------------------- + */ +int +Suff_EndTransform(void *gnp, void *dummy MAKE_ATTR_UNUSED) +{ + GNode *gn = (GNode *)gnp; + + if ((gn->type & OP_DOUBLEDEP) && !Lst_IsEmpty (gn->cohorts)) + gn = (GNode *)Lst_Datum(Lst_Last(gn->cohorts)); + if ((gn->type & OP_TRANSFORM) && Lst_IsEmpty(gn->commands) && + Lst_IsEmpty(gn->children)) + { + Suff *s, *t; + + /* + * SuffParseTransform() may fail for special rules which are not + * actual transformation rules. (e.g. .DEFAULT) + */ + if (SuffParseTransform(gn->name, &s, &t)) { + Lst p; + + if (DEBUG(SUFF)) { + fprintf(debug_file, "deleting transformation from `%s' to `%s'\n", + s->name, t->name); + } + + /* + * Store s->parents because s could be deleted in SuffRemove + */ + p = s->parents; + + /* + * Remove the source from the target's children list. We check for a + * nil return to handle a beanhead saying something like + * .c.o .c.o: + * + * We'll be called twice when the next target is seen, but .c and .o + * are only linked once... + */ + SuffRemove(t->children, s); + + /* + * Remove the target from the source's parents list + */ + SuffRemove(p, t); + } + } else if ((gn->type & OP_TRANSFORM) && DEBUG(SUFF)) { + fprintf(debug_file, "transformation %s complete\n", gn->name); + } + + return 0; +} + +/*- + *----------------------------------------------------------------------- + * SuffRebuildGraph -- + * Called from Suff_AddSuffix via Lst_ForEach to search through the + * list of existing transformation rules and rebuild the transformation + * graph when it has been destroyed by Suff_ClearSuffixes. If the + * given rule is a transformation involving this suffix and another, + * existing suffix, the proper relationship is established between + * the two. + * + * Input: + * transformp Transformation to test + * sp Suffix to rebuild + * + * Results: + * Always 0. + * + * Side Effects: + * The appropriate links will be made between this suffix and + * others if transformation rules exist for it. + * + *----------------------------------------------------------------------- + */ +static int +SuffRebuildGraph(void *transformp, void *sp) +{ + GNode *transform = (GNode *)transformp; + Suff *s = (Suff *)sp; + char *cp; + LstNode ln; + Suff *s2; + SuffixCmpData sd; + + /* + * First see if it is a transformation from this suffix. + */ + cp = UNCONST(SuffStrIsPrefix(s->name, transform->name)); + if (cp != NULL) { + ln = Lst_Find(sufflist, cp, SuffSuffHasNameP); + if (ln != NULL) { + /* + * Found target. Link in and return, since it can't be anything + * else. + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s2->children, s); + SuffInsert(s->parents, s2); + return(0); + } + } + + /* + * Not from, maybe to? + */ + sd.len = strlen(transform->name); + sd.ename = transform->name + sd.len; + cp = SuffSuffIsSuffix(s, &sd); + if (cp != NULL) { + /* + * Null-terminate the source suffix in order to find it. + */ + cp[1] = '\0'; + ln = Lst_Find(sufflist, transform->name, SuffSuffHasNameP); + /* + * Replace the start of the target suffix + */ + cp[1] = s->name[0]; + if (ln != NULL) { + /* + * Found it -- establish the proper relationship + */ + s2 = (Suff *)Lst_Datum(ln); + SuffInsert(s->children, s2); + SuffInsert(s2->parents, s); + } + } + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffScanTargets -- + * Called from Suff_AddSuffix via Lst_ForEach to search through the + * list of existing targets and find if any of the existing targets + * can be turned into a transformation rule. + * + * Results: + * 1 if a new main target has been selected, 0 otherwise. + * + * Side Effects: + * If such a target is found and the target is the current main + * target, the main target is set to NULL and the next target + * examined (if that exists) becomes the main target. + * + *----------------------------------------------------------------------- + */ +static int +SuffScanTargets(void *targetp, void *gsp) +{ + GNode *target = (GNode *)targetp; + GNodeSuff *gs = (GNodeSuff *)gsp; + Suff *s, *t; + char *ptr; + + if (*gs->gn == NULL && gs->r && (target->type & OP_NOTARGET) == 0) { + *gs->gn = target; + Targ_SetMain(target); + return 1; + } + + if ((unsigned int)target->type == OP_TRANSFORM) + return 0; + + if ((ptr = strstr(target->name, gs->s->name)) == NULL || + ptr == target->name) + return 0; + + if (SuffParseTransform(target->name, &s, &t)) { + if (*gs->gn == target) { + gs->r = TRUE; + *gs->gn = NULL; + Targ_SetMain(NULL); + } + Lst_Destroy(target->children, NULL); + target->children = Lst_Init(FALSE); + target->type = OP_TRANSFORM; + /* + * link the two together in the proper relationship and order + */ + if (DEBUG(SUFF)) { + fprintf(debug_file, "defining transformation from `%s' to `%s'\n", + s->name, t->name); + } + SuffInsert(t->children, s); + SuffInsert(s->parents, t); + } + return 0; +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddSuffix -- + * Add the suffix in string to the end of the list of known suffixes. + * Should we restructure the suffix graph? Make doesn't... + * + * Input: + * str the name of the suffix to add + * + * Results: + * None + * + * Side Effects: + * A GNode is created for the suffix and a Suff structure is created and + * added to the suffixes list unless the suffix was already known. + * The mainNode passed can be modified if a target mutated into a + * transform and that target happened to be the main target. + *----------------------------------------------------------------------- + */ +void +Suff_AddSuffix(char *str, GNode **gn) +{ + Suff *s; /* new suffix descriptor */ + LstNode ln; + GNodeSuff gs; + + ln = Lst_Find(sufflist, str, SuffSuffHasNameP); + if (ln == NULL) { + s = bmake_malloc(sizeof(Suff)); + + s->name = bmake_strdup(str); + s->nameLen = strlen(s->name); + s->searchPath = Lst_Init(FALSE); + s->children = Lst_Init(FALSE); + s->parents = Lst_Init(FALSE); + s->ref = Lst_Init(FALSE); + s->sNum = sNum++; + s->flags = 0; + s->refCount = 1; + + (void)Lst_AtEnd(sufflist, s); + /* + * We also look at our existing targets list to see if adding + * this suffix will make one of our current targets mutate into + * a suffix rule. This is ugly, but other makes treat all targets + * that start with a . as suffix rules. + */ + gs.gn = gn; + gs.s = s; + gs.r = FALSE; + Lst_ForEach(Targ_List(), SuffScanTargets, &gs); + /* + * Look for any existing transformations from or to this suffix. + * XXX: Only do this after a Suff_ClearSuffixes? + */ + Lst_ForEach(transforms, SuffRebuildGraph, s); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_GetPath -- + * Return the search path for the given suffix, if it's defined. + * + * Results: + * The searchPath for the desired suffix or NULL if the suffix isn't + * defined. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Lst +Suff_GetPath(char *sname) +{ + LstNode ln; + Suff *s; + + ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); + if (ln == NULL) { + return NULL; + } else { + s = (Suff *)Lst_Datum(ln); + return (s->searchPath); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_DoPaths -- + * Extend the search paths for all suffixes to include the default + * search path. + * + * Results: + * None. + * + * Side Effects: + * The searchPath field of all the suffixes is extended by the + * directories in dirSearchPath. If paths were specified for the + * ".h" suffix, the directories are stuffed into a global variable + * called ".INCLUDES" with each directory preceded by a -I. The same + * is done for the ".a" suffix, except the variable is called + * ".LIBS" and the flag is -L. + *----------------------------------------------------------------------- + */ +void +Suff_DoPaths(void) +{ + Suff *s; + LstNode ln; + char *ptr; + Lst inIncludes; /* Cumulative .INCLUDES path */ + Lst inLibs; /* Cumulative .LIBS path */ + + if (Lst_Open(sufflist) == FAILURE) { + return; + } + + inIncludes = Lst_Init(FALSE); + inLibs = Lst_Init(FALSE); + + while ((ln = Lst_Next(sufflist)) != NULL) { + s = (Suff *)Lst_Datum(ln); + if (!Lst_IsEmpty (s->searchPath)) { +#ifdef INCLUDES + if (s->flags & SUFF_INCLUDE) { + Dir_Concat(inIncludes, s->searchPath); + } +#endif /* INCLUDES */ +#ifdef LIBRARIES + if (s->flags & SUFF_LIBRARY) { + Dir_Concat(inLibs, s->searchPath); + } +#endif /* LIBRARIES */ + Dir_Concat(s->searchPath, dirSearchPath); + } else { + Lst_Destroy(s->searchPath, Dir_Destroy); + s->searchPath = Lst_Duplicate(dirSearchPath, Dir_CopyDir); + } + } + + Var_Set(".INCLUDES", ptr = Dir_MakeFlags("-I", inIncludes), VAR_GLOBAL, 0); + free(ptr); + Var_Set(".LIBS", ptr = Dir_MakeFlags("-L", inLibs), VAR_GLOBAL, 0); + free(ptr); + + Lst_Destroy(inIncludes, Dir_Destroy); + Lst_Destroy(inLibs, Dir_Destroy); + + Lst_Close(sufflist); +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddInclude -- + * Add the given suffix as a type of file which gets included. + * Called from the parse module when a .INCLUDES line is parsed. + * The suffix must have already been defined. + * + * Input: + * sname Name of the suffix to mark + * + * Results: + * None. + * + * Side Effects: + * The SUFF_INCLUDE bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddInclude(char *sname) +{ + LstNode ln; + Suff *s; + + ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); + if (ln != NULL) { + s = (Suff *)Lst_Datum(ln); + s->flags |= SUFF_INCLUDE; + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_AddLib -- + * Add the given suffix as a type of file which is a library. + * Called from the parse module when parsing a .LIBS line. The + * suffix must have been defined via .SUFFIXES before this is + * called. + * + * Input: + * sname Name of the suffix to mark + * + * Results: + * None. + * + * Side Effects: + * The SUFF_LIBRARY bit is set in the suffix's flags field + * + *----------------------------------------------------------------------- + */ +void +Suff_AddLib(char *sname) +{ + LstNode ln; + Suff *s; + + ln = Lst_Find(sufflist, sname, SuffSuffHasNameP); + if (ln != NULL) { + s = (Suff *)Lst_Datum(ln); + s->flags |= SUFF_LIBRARY; + } +} + + /********** Implicit Source Search Functions *********/ + +/*- + *----------------------------------------------------------------------- + * SuffAddSrc -- + * Add a suffix as a Src structure to the given list with its parent + * being the given Src structure. If the suffix is the null suffix, + * the prefix is used unaltered as the file name in the Src structure. + * + * Input: + * sp suffix for which to create a Src structure + * lsp list and parent for the new Src + * + * Results: + * always returns 0 + * + * Side Effects: + * A Src structure is created and tacked onto the end of the list + *----------------------------------------------------------------------- + */ +static int +SuffAddSrc(void *sp, void *lsp) +{ + Suff *s = (Suff *)sp; + LstSrc *ls = (LstSrc *)lsp; + Src *s2; /* new Src structure */ + Src *targ; /* Target structure */ + + targ = ls->s; + + if ((s->flags & SUFF_NULL) && (*s->name != '\0')) { + /* + * If the suffix has been marked as the NULL suffix, also create a Src + * structure for a file with no suffix attached. Two birds, and all + * that... + */ + s2 = bmake_malloc(sizeof(Src)); + s2->file = bmake_strdup(targ->pref); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NULL; + s2->suff = s; + s->refCount++; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd(ls->l, s2); +#ifdef DEBUG_SRC + s2->cp = Lst_Init(FALSE); + Lst_AtEnd(targ->cp, s2); + fprintf(debug_file, "1 add %p %p to %p:", targ, s2, ls->l); + Lst_ForEach(ls->l, PrintAddr, NULL); + fprintf(debug_file, "\n"); +#endif + } + s2 = bmake_malloc(sizeof(Src)); + s2->file = str_concat(targ->pref, s->name, 0); + s2->pref = targ->pref; + s2->parent = targ; + s2->node = NULL; + s2->suff = s; + s->refCount++; + s2->children = 0; + targ->children += 1; + (void)Lst_AtEnd(ls->l, s2); +#ifdef DEBUG_SRC + s2->cp = Lst_Init(FALSE); + Lst_AtEnd(targ->cp, s2); + fprintf(debug_file, "2 add %p %p to %p:", targ, s2, ls->l); + Lst_ForEach(ls->l, PrintAddr, NULL); + fprintf(debug_file, "\n"); +#endif + + return(0); +} + +/*- + *----------------------------------------------------------------------- + * SuffAddLevel -- + * Add all the children of targ as Src structures to the given list + * + * Input: + * l list to which to add the new level + * targ Src structure to use as the parent + * + * Results: + * None + * + * Side Effects: + * Lots of structures are created and added to the list + *----------------------------------------------------------------------- + */ +static void +SuffAddLevel(Lst l, Src *targ) +{ + LstSrc ls; + + ls.s = targ; + ls.l = l; + + Lst_ForEach(targ->suff->children, SuffAddSrc, &ls); +} + +/*- + *---------------------------------------------------------------------- + * SuffRemoveSrc -- + * Free all src structures in list that don't have a reference count + * + * Results: + * Ture if an src was removed + * + * Side Effects: + * The memory is free'd. + *---------------------------------------------------------------------- + */ +static int +SuffRemoveSrc(Lst l) +{ + LstNode ln; + Src *s; + int t = 0; + + if (Lst_Open(l) == FAILURE) { + return 0; + } +#ifdef DEBUG_SRC + fprintf(debug_file, "cleaning %lx: ", (unsigned long) l); + Lst_ForEach(l, PrintAddr, NULL); + fprintf(debug_file, "\n"); +#endif + + + while ((ln = Lst_Next(l)) != NULL) { + s = (Src *)Lst_Datum(ln); + if (s->children == 0) { + free(s->file); + if (!s->parent) + free(s->pref); + else { +#ifdef DEBUG_SRC + LstNode ln2 = Lst_Member(s->parent->cp, s); + if (ln2 != NULL) + Lst_Remove(s->parent->cp, ln2); +#endif + --s->parent->children; + } +#ifdef DEBUG_SRC + fprintf(debug_file, "free: [l=%p] p=%p %d\n", l, s, s->children); + Lst_Destroy(s->cp, NULL); +#endif + Lst_Remove(l, ln); + free(s); + t |= 1; + Lst_Close(l); + return TRUE; + } +#ifdef DEBUG_SRC + else { + fprintf(debug_file, "keep: [l=%p] p=%p %d: ", l, s, s->children); + Lst_ForEach(s->cp, PrintAddr, NULL); + fprintf(debug_file, "\n"); + } +#endif + } + + Lst_Close(l); + + return t; +} + +/*- + *----------------------------------------------------------------------- + * SuffFindThem -- + * Find the first existing file/target in the list srcs + * + * Input: + * srcs list of Src structures to search through + * + * Results: + * The lowest structure in the chain of transformations + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Src * +SuffFindThem(Lst srcs, Lst slst) +{ + Src *s; /* current Src */ + Src *rs; /* returned Src */ + char *ptr; + + rs = NULL; + + while (!Lst_IsEmpty (srcs)) { + s = (Src *)Lst_DeQueue(srcs); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "\ttrying %s...", s->file); + } + + /* + * A file is considered to exist if either a node exists in the + * graph for it or the file actually exists. + */ + if (Targ_FindNode(s->file, TARG_NOCREATE) != NULL) { +#ifdef DEBUG_SRC + fprintf(debug_file, "remove %p from %p\n", s, srcs); +#endif + rs = s; + break; + } + + if ((ptr = Dir_FindFile(s->file, s->suff->searchPath)) != NULL) { + rs = s; +#ifdef DEBUG_SRC + fprintf(debug_file, "remove %p from %p\n", s, srcs); +#endif + free(ptr); + break; + } + + if (DEBUG(SUFF)) { + fprintf(debug_file, "not there\n"); + } + + SuffAddLevel(srcs, s); + Lst_AtEnd(slst, s); + } + + if (DEBUG(SUFF) && rs) { + fprintf(debug_file, "got it\n"); + } + return (rs); +} + +/*- + *----------------------------------------------------------------------- + * SuffFindCmds -- + * See if any of the children of the target in the Src structure is + * one from which the target can be transformed. If there is one, + * a Src structure is put together for it and returned. + * + * Input: + * targ Src structure to play with + * + * Results: + * The Src structure of the "winning" child, or NULL if no such beast. + * + * Side Effects: + * A Src structure may be allocated. + * + *----------------------------------------------------------------------- + */ +static Src * +SuffFindCmds(Src *targ, Lst slst) +{ + LstNode ln; /* General-purpose list node */ + GNode *t, /* Target GNode */ + *s; /* Source GNode */ + int prefLen;/* The length of the defined prefix */ + Suff *suff; /* Suffix on matching beastie */ + Src *ret; /* Return value */ + char *cp; + + t = targ->node; + (void)Lst_Open(t->children); + prefLen = strlen(targ->pref); + + for (;;) { + ln = Lst_Next(t->children); + if (ln == NULL) { + Lst_Close(t->children); + return NULL; + } + s = (GNode *)Lst_Datum(ln); + + if (s->type & OP_OPTIONAL && Lst_IsEmpty(t->commands)) { + /* + * We haven't looked to see if .OPTIONAL files exist yet, so + * don't use one as the implicit source. + * This allows us to use .OPTIONAL in .depend files so make won't + * complain "don't know how to make xxx.h' when a dependent file + * has been moved/deleted. + */ + continue; + } + + cp = strrchr(s->name, '/'); + if (cp == NULL) { + cp = s->name; + } else { + cp++; + } + if (strncmp(cp, targ->pref, prefLen) != 0) + continue; + /* + * The node matches the prefix ok, see if it has a known + * suffix. + */ + ln = Lst_Find(sufflist, &cp[prefLen], SuffSuffHasNameP); + if (ln == NULL) + continue; + /* + * It even has a known suffix, see if there's a transformation + * defined between the node's suffix and the target's suffix. + * + * XXX: Handle multi-stage transformations here, too. + */ + suff = (Suff *)Lst_Datum(ln); + + if (Lst_Member(suff->parents, targ->suff) != NULL) + break; + } + + /* + * Hot Damn! Create a new Src structure to describe + * this transformation (making sure to duplicate the + * source node's name so Suff_FindDeps can free it + * again (ick)), and return the new structure. + */ + ret = bmake_malloc(sizeof(Src)); + ret->file = bmake_strdup(s->name); + ret->pref = targ->pref; + ret->suff = suff; + suff->refCount++; + ret->parent = targ; + ret->node = s; + ret->children = 0; + targ->children += 1; +#ifdef DEBUG_SRC + ret->cp = Lst_Init(FALSE); + fprintf(debug_file, "3 add %p %p\n", targ, ret); + Lst_AtEnd(targ->cp, ret); +#endif + Lst_AtEnd(slst, ret); + if (DEBUG(SUFF)) { + fprintf(debug_file, "\tusing existing source %s\n", s->name); + } + return (ret); +} + +/*- + *----------------------------------------------------------------------- + * SuffExpandChildren -- + * Expand the names of any children of a given node that contain + * variable invocations or file wildcards into actual targets. + * + * Input: + * cln Child to examine + * pgn Parent node being processed + * + * Results: + * === 0 (continue) + * + * Side Effects: + * The expanded node is removed from the parent's list of children, + * and the parent's unmade counter is decremented, but other nodes + * may be added. + * + *----------------------------------------------------------------------- + */ +static void +SuffExpandChildren(LstNode cln, GNode *pgn) +{ + GNode *cgn = (GNode *)Lst_Datum(cln); + GNode *gn; /* New source 8) */ + char *cp; /* Expanded value */ + + if (!Lst_IsEmpty(cgn->order_pred) || !Lst_IsEmpty(cgn->order_succ)) + /* It is all too hard to process the result of .ORDER */ + return; + + if (cgn->type & OP_WAIT) + /* Ignore these (& OP_PHONY ?) */ + return; + + /* + * First do variable expansion -- this takes precedence over + * wildcard expansion. If the result contains wildcards, they'll be gotten + * to later since the resulting words are tacked on to the end of + * the children list. + */ + if (strchr(cgn->name, '$') == NULL) { + SuffExpandWildcards(cln, pgn); + return; + } + + if (DEBUG(SUFF)) { + fprintf(debug_file, "Expanding \"%s\"...", cgn->name); + } + cp = Var_Subst(NULL, cgn->name, pgn, VARF_UNDEFERR|VARF_WANTRES); + + if (cp != NULL) { + Lst members = Lst_Init(FALSE); + + if (cgn->type & OP_ARCHV) { + /* + * Node was an archive(member) target, so we want to call + * on the Arch module to find the nodes for us, expanding + * variables in the parent's context. + */ + char *sacrifice = cp; + + (void)Arch_ParseArchive(&sacrifice, members, pgn); + } else { + /* + * Break the result into a vector of strings whose nodes + * we can find, then add those nodes to the members list. + * Unfortunately, we can't use brk_string b/c it + * doesn't understand about variable specifications with + * spaces in them... + */ + char *start; + char *initcp = cp; /* For freeing... */ + + for (start = cp; *start == ' ' || *start == '\t'; start++) + continue; + for (cp = start; *cp != '\0'; cp++) { + if (*cp == ' ' || *cp == '\t') { + /* + * White-space -- terminate element, find the node, + * add it, skip any further spaces. + */ + *cp++ = '\0'; + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, gn); + while (*cp == ' ' || *cp == '\t') { + cp++; + } + /* + * Adjust cp for increment at start of loop, but + * set start to first non-space. + */ + start = cp--; + } else if (*cp == '$') { + /* + * Start of a variable spec -- contact variable module + * to find the end so we can skip over it. + */ + char *junk; + int len; + void *freeIt; + + junk = Var_Parse(cp, pgn, VARF_UNDEFERR|VARF_WANTRES, + &len, &freeIt); + if (junk != var_Error) { + cp += len - 1; + } + + free(freeIt); + } else if (*cp == '\\' && cp[1] != '\0') { + /* + * Escaped something -- skip over it + */ + cp++; + } + } + + if (cp != start) { + /* + * Stuff left over -- add it to the list too + */ + gn = Targ_FindNode(start, TARG_CREATE); + (void)Lst_AtEnd(members, gn); + } + /* + * Point cp back at the beginning again so the variable value + * can be freed. + */ + cp = initcp; + } + + /* + * Add all elements of the members list to the parent node. + */ + while(!Lst_IsEmpty(members)) { + gn = (GNode *)Lst_DeQueue(members); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "%s...", gn->name); + } + /* Add gn to the parents child list before the original child */ + (void)Lst_InsertBefore(pgn->children, cln, gn); + (void)Lst_AtEnd(gn->parents, pgn); + pgn->unmade++; + /* Expand wildcards on new node */ + SuffExpandWildcards(Lst_Prev(cln), pgn); + } + Lst_Destroy(members, NULL); + + /* + * Free the result + */ + free(cp); + } + if (DEBUG(SUFF)) { + fprintf(debug_file, "\n"); + } + + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + pgn->unmade--; + Lst_Remove(pgn->children, cln); + Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); +} + +static void +SuffExpandWildcards(LstNode cln, GNode *pgn) +{ + GNode *cgn = (GNode *)Lst_Datum(cln); + GNode *gn; /* New source 8) */ + char *cp; /* Expanded value */ + Lst explist; /* List of expansions */ + + if (!Dir_HasWildcards(cgn->name)) + return; + + /* + * Expand the word along the chosen path + */ + explist = Lst_Init(FALSE); + Dir_Expand(cgn->name, Suff_FindPath(cgn), explist); + + while (!Lst_IsEmpty(explist)) { + /* + * Fetch next expansion off the list and find its GNode + */ + cp = (char *)Lst_DeQueue(explist); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "%s...", cp); + } + gn = Targ_FindNode(cp, TARG_CREATE); + + /* Add gn to the parents child list before the original child */ + (void)Lst_InsertBefore(pgn->children, cln, gn); + (void)Lst_AtEnd(gn->parents, pgn); + pgn->unmade++; + } + + /* + * Nuke what's left of the list + */ + Lst_Destroy(explist, NULL); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "\n"); + } + + /* + * Now the source is expanded, remove it from the list of children to + * keep it from being processed. + */ + pgn->unmade--; + Lst_Remove(pgn->children, cln); + Lst_Remove(cgn->parents, Lst_Member(cgn->parents, pgn)); +} + +/*- + *----------------------------------------------------------------------- + * Suff_FindPath -- + * Find a path along which to expand the node. + * + * If the word has a known suffix, use that path. + * If it has no known suffix, use the default system search path. + * + * Input: + * gn Node being examined + * + * Results: + * The appropriate path to search for the GNode. + * + * Side Effects: + * XXX: We could set the suffix here so that we don't have to scan + * again. + * + *----------------------------------------------------------------------- + */ +Lst +Suff_FindPath(GNode* gn) +{ + Suff *suff = gn->suffix; + + if (suff == NULL) { + SuffixCmpData sd; /* Search string data */ + LstNode ln; + sd.len = strlen(gn->name); + sd.ename = gn->name + sd.len; + ln = Lst_Find(sufflist, &sd, SuffSuffIsSuffixP); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "Wildcard expanding \"%s\"...", gn->name); + } + if (ln != NULL) + suff = (Suff *)Lst_Datum(ln); + /* XXX: Here we can save the suffix so we don't have to do this again */ + } + + if (suff != NULL) { + if (DEBUG(SUFF)) { + fprintf(debug_file, "suffix is \"%s\"...", suff->name); + } + return suff->searchPath; + } else { + /* + * Use default search path + */ + return dirSearchPath; + } +} + +/*- + *----------------------------------------------------------------------- + * SuffApplyTransform -- + * Apply a transformation rule, given the source and target nodes + * and suffixes. + * + * Input: + * tGn Target node + * sGn Source node + * t Target suffix + * s Source suffix + * + * Results: + * TRUE if successful, FALSE if not. + * + * Side Effects: + * The source and target are linked and the commands from the + * transformation are added to the target node's commands list. + * All attributes but OP_DEPMASK and OP_TRANSFORM are applied + * to the target. The target also inherits all the sources for + * the transformation rule. + * + *----------------------------------------------------------------------- + */ +static Boolean +SuffApplyTransform(GNode *tGn, GNode *sGn, Suff *t, Suff *s) +{ + LstNode ln, nln; /* General node */ + char *tname; /* Name of transformation rule */ + GNode *gn; /* Node for same */ + + /* + * Form the proper links between the target and source. + */ + (void)Lst_AtEnd(tGn->children, sGn); + (void)Lst_AtEnd(sGn->parents, tGn); + tGn->unmade += 1; + + /* + * Locate the transformation rule itself + */ + tname = str_concat(s->name, t->name, 0); + ln = Lst_Find(transforms, tname, SuffGNHasNameP); + free(tname); + + if (ln == NULL) { + /* + * Not really such a transformation rule (can happen when we're + * called to link an OP_MEMBER and OP_ARCHV node), so return + * FALSE. + */ + return(FALSE); + } + + gn = (GNode *)Lst_Datum(ln); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "\tapplying %s -> %s to \"%s\"\n", s->name, t->name, tGn->name); + } + + /* + * Record last child for expansion purposes + */ + ln = Lst_Last(tGn->children); + + /* + * Pass the buck to Make_HandleUse to apply the rule + */ + (void)Make_HandleUse(gn, tGn); + + /* + * Deal with wildcards and variables in any acquired sources + */ + for (ln = Lst_Succ(ln); ln != NULL; ln = nln) { + nln = Lst_Succ(ln); + SuffExpandChildren(ln, tGn); + } + + /* + * Keep track of another parent to which this beast is transformed so + * the .IMPSRC variable can be set correctly for the parent. + */ + (void)Lst_AtEnd(sGn->iParents, tGn); + + return(TRUE); +} + + +/*- + *----------------------------------------------------------------------- + * SuffFindArchiveDeps -- + * Locate dependencies for an OP_ARCHV node. + * + * Input: + * gn Node for which to locate dependencies + * + * Results: + * None + * + * Side Effects: + * Same as Suff_FindDeps + * + *----------------------------------------------------------------------- + */ +static void +SuffFindArchiveDeps(GNode *gn, Lst slst) +{ + char *eoarch; /* End of archive portion */ + char *eoname; /* End of member portion */ + GNode *mem; /* Node for member */ + static const char *copy[] = { + /* Variables to be copied from the member node */ + TARGET, /* Must be first */ + PREFIX, /* Must be second */ + }; + LstNode ln, nln; /* Next suffix node to check */ + int i; /* Index into copy and vals */ + Suff *ms; /* Suffix descriptor for member */ + char *name; /* Start of member's name */ + + /* + * The node is an archive(member) pair. so we must find a + * suffix for both of them. + */ + eoarch = strchr(gn->name, '('); + eoname = strchr(eoarch, ')'); + + /* + * Caller guarantees the format `libname(member)', via + * Arch_ParseArchive. + */ + assert(eoarch != NULL); + assert(eoname != NULL); + + *eoname = '\0'; /* Nuke parentheses during suffix search */ + *eoarch = '\0'; /* So a suffix can be found */ + + name = eoarch + 1; + + /* + * To simplify things, call Suff_FindDeps recursively on the member now, + * so we can simply compare the member's .PREFIX and .TARGET variables + * to locate its suffix. This allows us to figure out the suffix to + * use for the archive without having to do a quadratic search over the + * suffix list, backtracking for each one... + */ + mem = Targ_FindNode(name, TARG_CREATE); + SuffFindDeps(mem, slst); + + /* + * Create the link between the two nodes right off + */ + (void)Lst_AtEnd(gn->children, mem); + (void)Lst_AtEnd(mem->parents, gn); + gn->unmade += 1; + + /* + * Copy in the variables from the member node to this one. + */ + for (i = (sizeof(copy)/sizeof(copy[0]))-1; i >= 0; i--) { + char *p1; + Var_Set(copy[i], Var_Value(copy[i], mem, &p1), gn, 0); + free(p1); + + } + + ms = mem->suffix; + if (ms == NULL) { + /* + * Didn't know what it was -- use .NULL suffix if not in make mode + */ + if (DEBUG(SUFF)) { + fprintf(debug_file, "using null suffix\n"); + } + ms = suffNull; + } + + + /* + * Set the other two local variables required for this target. + */ + Var_Set(MEMBER, name, gn, 0); + Var_Set(ARCHIVE, gn->name, gn, 0); + + /* + * Set $@ for compatibility with other makes + */ + Var_Set(TARGET, gn->name, gn, 0); + + /* + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. + */ + for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { + nln = Lst_Succ(ln); + SuffExpandChildren(ln, gn); + } + + if (ms != NULL) { + /* + * Member has a known suffix, so look for a transformation rule from + * it to a possible suffix of the archive. Rather than searching + * through the entire list, we just look at suffixes to which the + * member's suffix may be transformed... + */ + SuffixCmpData sd; /* Search string data */ + + /* + * Use first matching suffix... + */ + sd.len = eoarch - gn->name; + sd.ename = eoarch; + ln = Lst_Find(ms->parents, &sd, SuffSuffIsSuffixP); + + if (ln != NULL) { + /* + * Got one -- apply it + */ + if (!SuffApplyTransform(gn, mem, (Suff *)Lst_Datum(ln), ms) && + DEBUG(SUFF)) + { + fprintf(debug_file, "\tNo transformation from %s -> %s\n", + ms->name, ((Suff *)Lst_Datum(ln))->name); + } + } + } + + /* + * Replace the opening and closing parens now we've no need of the separate + * pieces. + */ + *eoarch = '('; *eoname = ')'; + + /* + * Pretend gn appeared to the left of a dependency operator so + * the user needn't provide a transformation from the member to the + * archive. + */ + if (OP_NOP(gn->type)) { + gn->type |= OP_DEPENDS; + } + + /* + * Flag the member as such so we remember to look in the archive for + * its modification time. The OP_JOIN | OP_MADE is needed because this + * target should never get made. + */ + mem->type |= OP_MEMBER | OP_JOIN | OP_MADE; +} + +/*- + *----------------------------------------------------------------------- + * SuffFindNormalDeps -- + * Locate implicit dependencies for regular targets. + * + * Input: + * gn Node for which to find sources + * + * Results: + * None. + * + * Side Effects: + * Same as Suff_FindDeps... + * + *----------------------------------------------------------------------- + */ +static void +SuffFindNormalDeps(GNode *gn, Lst slst) +{ + char *eoname; /* End of name */ + char *sopref; /* Start of prefix */ + LstNode ln, nln; /* Next suffix node to check */ + Lst srcs; /* List of sources at which to look */ + Lst targs; /* List of targets to which things can be + * transformed. They all have the same file, + * but different suff and pref fields */ + Src *bottom; /* Start of found transformation path */ + Src *src; /* General Src pointer */ + char *pref; /* Prefix to use */ + Src *targ; /* General Src target pointer */ + SuffixCmpData sd; /* Search string data */ + + + sd.len = strlen(gn->name); + sd.ename = eoname = gn->name + sd.len; + + sopref = gn->name; + + /* + * Begin at the beginning... + */ + ln = Lst_First(sufflist); + srcs = Lst_Init(FALSE); + targs = Lst_Init(FALSE); + + /* + * We're caught in a catch-22 here. On the one hand, we want to use any + * transformation implied by the target's sources, but we can't examine + * the sources until we've expanded any variables/wildcards they may hold, + * and we can't do that until we've set up the target's local variables + * and we can't do that until we know what the proper suffix for the + * target is (in case there are two suffixes one of which is a suffix of + * the other) and we can't know that until we've found its implied + * source, which we may not want to use if there's an existing source + * that implies a different transformation. + * + * In an attempt to get around this, which may not work all the time, + * but should work most of the time, we look for implied sources first, + * checking transformations to all possible suffixes of the target, + * use what we find to set the target's local variables, expand the + * children, then look for any overriding transformations they imply. + * Should we find one, we discard the one we found before. + */ + bottom = NULL; + targ = NULL; + + if (!(gn->type & OP_PHONY)) { + + while (ln != NULL) { + /* + * Look for next possible suffix... + */ + ln = Lst_FindFrom(sufflist, ln, &sd, SuffSuffIsSuffixP); + + if (ln != NULL) { + int prefLen; /* Length of the prefix */ + + /* + * Allocate a Src structure to which things can be transformed + */ + targ = bmake_malloc(sizeof(Src)); + targ->file = bmake_strdup(gn->name); + targ->suff = (Suff *)Lst_Datum(ln); + targ->suff->refCount++; + targ->node = gn; + targ->parent = NULL; + targ->children = 0; +#ifdef DEBUG_SRC + targ->cp = Lst_Init(FALSE); +#endif + + /* + * Allocate room for the prefix, whose end is found by + * subtracting the length of the suffix from + * the end of the name. + */ + prefLen = (eoname - targ->suff->nameLen) - sopref; + targ->pref = bmake_malloc(prefLen + 1); + memcpy(targ->pref, sopref, prefLen); + targ->pref[prefLen] = '\0'; + + /* + * Add nodes from which the target can be made + */ + SuffAddLevel(srcs, targ); + + /* + * Record the target so we can nuke it + */ + (void)Lst_AtEnd(targs, targ); + + /* + * Search from this suffix's successor... + */ + ln = Lst_Succ(ln); + } + } + + /* + * Handle target of unknown suffix... + */ + if (Lst_IsEmpty(targs) && suffNull != NULL) { + if (DEBUG(SUFF)) { + fprintf(debug_file, "\tNo known suffix on %s. Using .NULL suffix\n", gn->name); + } + + targ = bmake_malloc(sizeof(Src)); + targ->file = bmake_strdup(gn->name); + targ->suff = suffNull; + targ->suff->refCount++; + targ->node = gn; + targ->parent = NULL; + targ->children = 0; + targ->pref = bmake_strdup(sopref); +#ifdef DEBUG_SRC + targ->cp = Lst_Init(FALSE); +#endif + + /* + * Only use the default suffix rules if we don't have commands + * defined for this gnode; traditional make programs used to + * not define suffix rules if the gnode had children but we + * don't do this anymore. + */ + if (Lst_IsEmpty(gn->commands)) + SuffAddLevel(srcs, targ); + else { + if (DEBUG(SUFF)) + fprintf(debug_file, "not "); + } + + if (DEBUG(SUFF)) + fprintf(debug_file, "adding suffix rules\n"); + + (void)Lst_AtEnd(targs, targ); + } + + /* + * Using the list of possible sources built up from the target + * suffix(es), try and find an existing file/target that matches. + */ + bottom = SuffFindThem(srcs, slst); + + if (bottom == NULL) { + /* + * No known transformations -- use the first suffix found + * for setting the local variables. + */ + if (!Lst_IsEmpty(targs)) { + targ = (Src *)Lst_Datum(Lst_First(targs)); + } else { + targ = NULL; + } + } else { + /* + * Work up the transformation path to find the suffix of the + * target to which the transformation was made. + */ + for (targ = bottom; targ->parent != NULL; targ = targ->parent) + continue; + } + } + + Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); + + pref = (targ != NULL) ? targ->pref : gn->name; + Var_Set(PREFIX, pref, gn, 0); + + /* + * Now we've got the important local variables set, expand any sources + * that still contain variables or wildcards in their names. + */ + for (ln = Lst_First(gn->children); ln != NULL; ln = nln) { + nln = Lst_Succ(ln); + SuffExpandChildren(ln, gn); + } + + if (targ == NULL) { + if (DEBUG(SUFF)) { + fprintf(debug_file, "\tNo valid suffix on %s\n", gn->name); + } + +sfnd_abort: + /* + * Deal with finding the thing on the default search path. We + * always do that, not only if the node is only a source (not + * on the lhs of a dependency operator or [XXX] it has neither + * children or commands) as the old pmake did. + */ + if ((gn->type & (OP_PHONY|OP_NOPATH)) == 0) { + free(gn->path); + gn->path = Dir_FindFile(gn->name, + (targ == NULL ? dirSearchPath : + targ->suff->searchPath)); + if (gn->path != NULL) { + char *ptr; + Var_Set(TARGET, gn->path, gn, 0); + + if (targ != NULL) { + /* + * Suffix known for the thing -- trim the suffix off + * the path to form the proper .PREFIX variable. + */ + int savep = strlen(gn->path) - targ->suff->nameLen; + char savec; + + if (gn->suffix) + gn->suffix->refCount--; + gn->suffix = targ->suff; + gn->suffix->refCount++; + + savec = gn->path[savep]; + gn->path[savep] = '\0'; + + if ((ptr = strrchr(gn->path, '/')) != NULL) + ptr++; + else + ptr = gn->path; + + Var_Set(PREFIX, ptr, gn, 0); + + gn->path[savep] = savec; + } else { + /* + * The .PREFIX gets the full path if the target has + * no known suffix. + */ + if (gn->suffix) + gn->suffix->refCount--; + gn->suffix = NULL; + + if ((ptr = strrchr(gn->path, '/')) != NULL) + ptr++; + else + ptr = gn->path; + + Var_Set(PREFIX, ptr, gn, 0); + } + } + } + + goto sfnd_return; + } + + /* + * If the suffix indicates that the target is a library, mark that in + * the node's type field. + */ + if (targ->suff->flags & SUFF_LIBRARY) { + gn->type |= OP_LIB; + } + + /* + * Check for overriding transformation rule implied by sources + */ + if (!Lst_IsEmpty(gn->children)) { + src = SuffFindCmds(targ, slst); + + if (src != NULL) { + /* + * Free up all the Src structures in the transformation path + * up to, but not including, the parent node. + */ + while (bottom && bottom->parent != NULL) { + if (Lst_Member(slst, bottom) == NULL) { + Lst_AtEnd(slst, bottom); + } + bottom = bottom->parent; + } + bottom = src; + } + } + + if (bottom == NULL) { + /* + * No idea from where it can come -- return now. + */ + goto sfnd_abort; + } + + /* + * We now have a list of Src structures headed by 'bottom' and linked via + * their 'parent' pointers. What we do next is create links between + * source and target nodes (which may or may not have been created) + * and set the necessary local variables in each target. The + * commands for each target are set from the commands of the + * transformation rule used to get from the src suffix to the targ + * suffix. Note that this causes the commands list of the original + * node, gn, to be replaced by the commands of the final + * transformation rule. Also, the unmade field of gn is incremented. + * Etc. + */ + if (bottom->node == NULL) { + bottom->node = Targ_FindNode(bottom->file, TARG_CREATE); + } + + for (src = bottom; src->parent != NULL; src = src->parent) { + targ = src->parent; + + if (src->node->suffix) + src->node->suffix->refCount--; + src->node->suffix = src->suff; + src->node->suffix->refCount++; + + if (targ->node == NULL) { + targ->node = Targ_FindNode(targ->file, TARG_CREATE); + } + + SuffApplyTransform(targ->node, src->node, + targ->suff, src->suff); + + if (targ->node != gn) { + /* + * Finish off the dependency-search process for any nodes + * between bottom and gn (no point in questing around the + * filesystem for their implicit source when it's already + * known). Note that the node can't have any sources that + * need expanding, since SuffFindThem will stop on an existing + * node, so all we need to do is set the standard and System V + * variables. + */ + targ->node->type |= OP_DEPS_FOUND; + + Var_Set(PREFIX, targ->pref, targ->node, 0); + + Var_Set(TARGET, targ->node->name, targ->node, 0); + } + } + + if (gn->suffix) + gn->suffix->refCount--; + gn->suffix = src->suff; + gn->suffix->refCount++; + + /* + * Nuke the transformation path and the Src structures left over in the + * two lists. + */ +sfnd_return: + if (bottom) + if (Lst_Member(slst, bottom) == NULL) + Lst_AtEnd(slst, bottom); + + while (SuffRemoveSrc(srcs) || SuffRemoveSrc(targs)) + continue; + + Lst_Concat(slst, srcs, LST_CONCLINK); + Lst_Concat(slst, targs, LST_CONCLINK); +} + + +/*- + *----------------------------------------------------------------------- + * Suff_FindDeps -- + * Find implicit sources for the target described by the graph node + * gn + * + * Results: + * Nothing. + * + * Side Effects: + * Nodes are added to the graph below the passed-in node. The nodes + * are marked to have their IMPSRC variable filled in. The + * PREFIX variable is set for the given node and all its + * implied children. + * + * Notes: + * The path found by this target is the shortest path in the + * transformation graph, which may pass through non-existent targets, + * to an existing target. The search continues on all paths from the + * root suffix until a file is found. I.e. if there's a path + * .o -> .c -> .l -> .l,v from the root and the .l,v file exists but + * the .c and .l files don't, the search will branch out in + * all directions from .o and again from all the nodes on the + * next level until the .l,v node is encountered. + * + *----------------------------------------------------------------------- + */ + +void +Suff_FindDeps(GNode *gn) +{ + + SuffFindDeps(gn, srclist); + while (SuffRemoveSrc(srclist)) + continue; +} + + +/* + * Input: + * gn node we're dealing with + * + */ +static void +SuffFindDeps(GNode *gn, Lst slst) +{ + if (gn->type & OP_DEPS_FOUND) { + /* + * If dependencies already found, no need to do it again... + */ + return; + } else { + gn->type |= OP_DEPS_FOUND; + } + /* + * Make sure we have these set, may get revised below. + */ + Var_Set(TARGET, gn->path ? gn->path : gn->name, gn, 0); + Var_Set(PREFIX, gn->name, gn, 0); + + if (DEBUG(SUFF)) { + fprintf(debug_file, "SuffFindDeps (%s)\n", gn->name); + } + + if (gn->type & OP_ARCHV) { + SuffFindArchiveDeps(gn, slst); + } else if (gn->type & OP_LIB) { + /* + * If the node is a library, it is the arch module's job to find it + * and set the TARGET variable accordingly. We merely provide the + * search path, assuming all libraries end in ".a" (if the suffix + * hasn't been defined, there's nothing we can do for it, so we just + * set the TARGET variable to the node's name in order to give it a + * value). + */ + LstNode ln; + Suff *s; + + ln = Lst_Find(sufflist, LIBSUFF, SuffSuffHasNameP); + if (gn->suffix) + gn->suffix->refCount--; + if (ln != NULL) { + gn->suffix = s = (Suff *)Lst_Datum(ln); + gn->suffix->refCount++; + Arch_FindLib(gn, s->searchPath); + } else { + gn->suffix = NULL; + Var_Set(TARGET, gn->name, gn, 0); + } + /* + * Because a library (-lfoo) target doesn't follow the standard + * filesystem conventions, we don't set the regular variables for + * the thing. .PREFIX is simply made empty... + */ + Var_Set(PREFIX, "", gn, 0); + } else { + SuffFindNormalDeps(gn, slst); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_SetNull -- + * Define which suffix is the null suffix. + * + * Input: + * name Name of null suffix + * + * Results: + * None. + * + * Side Effects: + * 'suffNull' is altered. + * + * Notes: + * Need to handle the changing of the null suffix gracefully so the + * old transformation rules don't just go away. + * + *----------------------------------------------------------------------- + */ +void +Suff_SetNull(char *name) +{ + Suff *s; + LstNode ln; + + ln = Lst_Find(sufflist, name, SuffSuffHasNameP); + if (ln != NULL) { + s = (Suff *)Lst_Datum(ln); + if (suffNull != NULL) { + suffNull->flags &= ~SUFF_NULL; + } + s->flags |= SUFF_NULL; + /* + * XXX: Here's where the transformation mangling would take place + */ + suffNull = s; + } else { + Parse_Error(PARSE_WARNING, "Desired null suffix %s not defined.", + name); + } +} + +/*- + *----------------------------------------------------------------------- + * Suff_Init -- + * Initialize suffixes module + * + * Results: + * None + * + * Side Effects: + * Many + *----------------------------------------------------------------------- + */ +void +Suff_Init(void) +{ +#ifdef CLEANUP + suffClean = Lst_Init(FALSE); +#endif + srclist = Lst_Init(FALSE); + transforms = Lst_Init(FALSE); + + /* + * Create null suffix for single-suffix rules (POSIX). The thing doesn't + * actually go on the suffix list or everyone will think that's its + * suffix. + */ + Suff_ClearSuffixes(); +} + + +/*- + *---------------------------------------------------------------------- + * Suff_End -- + * Cleanup the this module + * + * Results: + * None + * + * Side Effects: + * The memory is free'd. + *---------------------------------------------------------------------- + */ + +void +Suff_End(void) +{ +#ifdef CLEANUP + Lst_Destroy(sufflist, SuffFree); + Lst_Destroy(suffClean, SuffFree); + if (suffNull) + SuffFree(suffNull); + Lst_Destroy(srclist, NULL); + Lst_Destroy(transforms, NULL); +#endif +} + + +/********************* DEBUGGING FUNCTIONS **********************/ + +static int SuffPrintName(void *s, void *dummy MAKE_ATTR_UNUSED) +{ + + fprintf(debug_file, "%s ", ((Suff *)s)->name); + return 0; +} + +static int +SuffPrintSuff(void *sp, void *dummy MAKE_ATTR_UNUSED) +{ + Suff *s = (Suff *)sp; + int flags; + int flag; + + fprintf(debug_file, "# `%s' [%d] ", s->name, s->refCount); + + flags = s->flags; + if (flags) { + fputs(" (", debug_file); + while (flags) { + flag = 1 << (ffs(flags) - 1); + flags &= ~flag; + switch (flag) { + case SUFF_NULL: + fprintf(debug_file, "NULL"); + break; + case SUFF_INCLUDE: + fprintf(debug_file, "INCLUDE"); + break; + case SUFF_LIBRARY: + fprintf(debug_file, "LIBRARY"); + break; + } + fputc(flags ? '|' : ')', debug_file); + } + } + fputc('\n', debug_file); + fprintf(debug_file, "#\tTo: "); + Lst_ForEach(s->parents, SuffPrintName, NULL); + fputc('\n', debug_file); + fprintf(debug_file, "#\tFrom: "); + Lst_ForEach(s->children, SuffPrintName, NULL); + fputc('\n', debug_file); + fprintf(debug_file, "#\tSearch Path: "); + Dir_PrintPath(s->searchPath); + fputc('\n', debug_file); + return 0; +} + +static int +SuffPrintTrans(void *tp, void *dummy MAKE_ATTR_UNUSED) +{ + GNode *t = (GNode *)tp; + + fprintf(debug_file, "%-16s: ", t->name); + Targ_PrintType(t->type); + fputc('\n', debug_file); + Lst_ForEach(t->commands, Targ_PrintCmd, NULL); + fputc('\n', debug_file); + return 0; +} + +void +Suff_PrintAll(void) +{ + fprintf(debug_file, "#*** Suffixes:\n"); + Lst_ForEach(sufflist, SuffPrintSuff, NULL); + + fprintf(debug_file, "#*** Transformations:\n"); + Lst_ForEach(transforms, SuffPrintTrans, NULL); +} diff --git a/usr.bin/make/targ.c b/usr.bin/make/targ.c new file mode 100644 index 0000000..01c3d1c --- /dev/null +++ b/usr.bin/make/targ.c @@ -0,0 +1,846 @@ +/* $NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)targ.c 8.2 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: targ.c,v 1.62 2017/04/16 19:53:58 riastradh Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * targ.c -- + * Functions for maintaining the Lst allTargets. Target nodes are + * kept in two structures: a Lst, maintained by the list library, and a + * hash table, maintained by the hash library. + * + * Interface: + * Targ_Init Initialization procedure. + * + * Targ_End Cleanup the module + * + * Targ_List Return the list of all targets so far. + * + * Targ_NewGN Create a new GNode for the passed target + * (string). The node is *not* placed in the + * hash table, though all its fields are + * initialized. + * + * Targ_FindNode Find the node for a given target, creating + * and storing it if it doesn't exist and the + * flags are right (TARG_CREATE) + * + * Targ_FindList Given a list of names, find nodes for all + * of them. If a name doesn't exist and the + * TARG_NOCREATE flag was given, an error message + * is printed. Else, if a name doesn't exist, + * its node is created. + * + * Targ_Ignore Return TRUE if errors should be ignored when + * creating the given target. + * + * Targ_Silent Return TRUE if we should be silent when + * creating the given target. + * + * Targ_Precious Return TRUE if the target is precious and + * should not be removed if we are interrupted. + * + * Targ_Propagate Propagate information between related + * nodes. Should be called after the + * makefiles are parsed but before any + * action is taken. + * + * Debugging: + * Targ_PrintGraph Print out the entire graphm all variables + * and statistics for the directory cache. Should + * print something for suffixes, too, but... + */ + +#include +#include + +#include "make.h" +#include "hash.h" +#include "dir.h" + +static Lst allTargets; /* the list of all targets found so far */ +#ifdef CLEANUP +static Lst allGNs; /* List of all the GNodes */ +#endif +static Hash_Table targets; /* a hash table of same */ + +#define HTSIZE 191 /* initial size of hash table */ + +static int TargPrintOnlySrc(void *, void *); +static int TargPrintName(void *, void *); +#ifdef CLEANUP +static void TargFreeGN(void *); +#endif +static int TargPropagateCohort(void *, void *); +static int TargPropagateNode(void *, void *); + +/*- + *----------------------------------------------------------------------- + * Targ_Init -- + * Initialize this module + * + * Results: + * None + * + * Side Effects: + * The allTargets list and the targets hash table are initialized + *----------------------------------------------------------------------- + */ +void +Targ_Init(void) +{ + allTargets = Lst_Init(FALSE); + Hash_InitTable(&targets, HTSIZE); +} + +/*- + *----------------------------------------------------------------------- + * Targ_End -- + * Finalize this module + * + * Results: + * None + * + * Side Effects: + * All lists and gnodes are cleared + *----------------------------------------------------------------------- + */ +void +Targ_End(void) +{ +#ifdef CLEANUP + Lst_Destroy(allTargets, NULL); + if (allGNs) + Lst_Destroy(allGNs, TargFreeGN); + Hash_DeleteTable(&targets); +#endif +} + +/*- + *----------------------------------------------------------------------- + * Targ_List -- + * Return the list of all targets + * + * Results: + * The list of all targets. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Lst +Targ_List(void) +{ + return allTargets; +} + +/*- + *----------------------------------------------------------------------- + * Targ_NewGN -- + * Create and initialize a new graph node + * + * Input: + * name the name to stick in the new node + * + * Results: + * An initialized graph node with the name field filled with a copy + * of the passed name + * + * Side Effects: + * The gnode is added to the list of all gnodes. + *----------------------------------------------------------------------- + */ +GNode * +Targ_NewGN(const char *name) +{ + GNode *gn; + + gn = bmake_malloc(sizeof(GNode)); + gn->name = bmake_strdup(name); + gn->uname = NULL; + gn->path = NULL; + if (name[0] == '-' && name[1] == 'l') { + gn->type = OP_LIB; + } else { + gn->type = 0; + } + gn->unmade = 0; + gn->unmade_cohorts = 0; + gn->cohort_num[0] = 0; + gn->centurion = NULL; + gn->made = UNMADE; + gn->flags = 0; + gn->checked = 0; + gn->mtime = 0; + gn->cmgn = NULL; + gn->iParents = Lst_Init(FALSE); + gn->cohorts = Lst_Init(FALSE); + gn->parents = Lst_Init(FALSE); + gn->children = Lst_Init(FALSE); + gn->order_pred = Lst_Init(FALSE); + gn->order_succ = Lst_Init(FALSE); + Hash_InitTable(&gn->context, 0); + gn->commands = Lst_Init(FALSE); + gn->suffix = NULL; + gn->lineno = 0; + gn->fname = NULL; + +#ifdef CLEANUP + if (allGNs == NULL) + allGNs = Lst_Init(FALSE); + Lst_AtEnd(allGNs, gn); +#endif + + return (gn); +} + +#ifdef CLEANUP +/*- + *----------------------------------------------------------------------- + * TargFreeGN -- + * Destroy a GNode + * + * Results: + * None. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +static void +TargFreeGN(void *gnp) +{ + GNode *gn = (GNode *)gnp; + + + free(gn->name); + free(gn->uname); + free(gn->path); + /* gn->fname points to name allocated when file was opened, don't free */ + + Lst_Destroy(gn->iParents, NULL); + Lst_Destroy(gn->cohorts, NULL); + Lst_Destroy(gn->parents, NULL); + Lst_Destroy(gn->children, NULL); + Lst_Destroy(gn->order_succ, NULL); + Lst_Destroy(gn->order_pred, NULL); + Hash_DeleteTable(&gn->context); + Lst_Destroy(gn->commands, NULL); + free(gn); +} +#endif + + +/*- + *----------------------------------------------------------------------- + * Targ_FindNode -- + * Find a node in the list using the given name for matching + * + * Input: + * name the name to find + * flags flags governing events when target not + * found + * + * Results: + * The node in the list if it was. If it wasn't, return NULL of + * flags was TARG_NOCREATE or the newly created and initialized node + * if it was TARG_CREATE + * + * Side Effects: + * Sometimes a node is created and added to the list + *----------------------------------------------------------------------- + */ +GNode * +Targ_FindNode(const char *name, int flags) +{ + GNode *gn; /* node in that element */ + Hash_Entry *he = NULL; /* New or used hash entry for node */ + Boolean isNew; /* Set TRUE if Hash_CreateEntry had to create */ + /* an entry for the node */ + + if (!(flags & (TARG_CREATE | TARG_NOHASH))) { + he = Hash_FindEntry(&targets, name); + if (he == NULL) + return NULL; + return (GNode *)Hash_GetValue(he); + } + + if (!(flags & TARG_NOHASH)) { + he = Hash_CreateEntry(&targets, name, &isNew); + if (!isNew) + return (GNode *)Hash_GetValue(he); + } + + gn = Targ_NewGN(name); + if (!(flags & TARG_NOHASH)) + Hash_SetValue(he, gn); + Var_Append(".ALLTARGETS", name, VAR_GLOBAL); + (void)Lst_AtEnd(allTargets, gn); + if (doing_depend) + gn->flags |= FROM_DEPEND; + return gn; +} + +/*- + *----------------------------------------------------------------------- + * Targ_FindList -- + * Make a complete list of GNodes from the given list of names + * + * Input: + * name list of names to find + * flags flags used if no node is found for a given name + * + * Results: + * A complete list of graph nodes corresponding to all instances of all + * the names in names. + * + * Side Effects: + * If flags is TARG_CREATE, nodes will be created for all names in + * names which do not yet have graph nodes. If flags is TARG_NOCREATE, + * an error message will be printed for each name which can't be found. + * ----------------------------------------------------------------------- + */ +Lst +Targ_FindList(Lst names, int flags) +{ + Lst nodes; /* result list */ + LstNode ln; /* name list element */ + GNode *gn; /* node in tLn */ + char *name; + + nodes = Lst_Init(FALSE); + + if (Lst_Open(names) == FAILURE) { + return (nodes); + } + while ((ln = Lst_Next(names)) != NULL) { + name = (char *)Lst_Datum(ln); + gn = Targ_FindNode(name, flags); + if (gn != NULL) { + /* + * Note: Lst_AtEnd must come before the Lst_Concat so the nodes + * are added to the list in the order in which they were + * encountered in the makefile. + */ + (void)Lst_AtEnd(nodes, gn); + } else if (flags == TARG_NOCREATE) { + Error("\"%s\" -- target unknown.", name); + } + } + Lst_Close(names); + return (nodes); +} + +/*- + *----------------------------------------------------------------------- + * Targ_Ignore -- + * Return true if should ignore errors when creating gn + * + * Input: + * gn node to check for + * + * Results: + * TRUE if should ignore errors + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Ignore(GNode *gn) +{ + if (ignoreErrors || gn->type & OP_IGNORE) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Silent -- + * Return true if be silent when creating gn + * + * Input: + * gn node to check for + * + * Results: + * TRUE if should be silent + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Silent(GNode *gn) +{ + if (beSilent || gn->type & OP_SILENT) { + return (TRUE); + } else { + return (FALSE); + } +} + +/*- + *----------------------------------------------------------------------- + * Targ_Precious -- + * See if the given target is precious + * + * Input: + * gn the node to check + * + * Results: + * TRUE if it is precious. FALSE otherwise + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +Boolean +Targ_Precious(GNode *gn) +{ + if (allPrecious || (gn->type & (OP_PRECIOUS|OP_DOUBLEDEP))) { + return (TRUE); + } else { + return (FALSE); + } +} + +/******************* DEBUG INFO PRINTING ****************/ + +static GNode *mainTarg; /* the main target, as set by Targ_SetMain */ +/*- + *----------------------------------------------------------------------- + * Targ_SetMain -- + * Set our idea of the main target we'll be creating. Used for + * debugging output. + * + * Input: + * gn The main target we'll create + * + * Results: + * None. + * + * Side Effects: + * "mainTarg" is set to the main target's node. + *----------------------------------------------------------------------- + */ +void +Targ_SetMain(GNode *gn) +{ + mainTarg = gn; +} + +static int +TargPrintName(void *gnp, void *pflags MAKE_ATTR_UNUSED) +{ + GNode *gn = (GNode *)gnp; + + fprintf(debug_file, "%s%s ", gn->name, gn->cohort_num); + + return 0; +} + + +int +Targ_PrintCmd(void *cmd, void *dummy MAKE_ATTR_UNUSED) +{ + fprintf(debug_file, "\t%s\n", (char *)cmd); + return 0; +} + +/*- + *----------------------------------------------------------------------- + * Targ_FmtTime -- + * Format a modification time in some reasonable way and return it. + * + * Results: + * The time reformatted. + * + * Side Effects: + * The time is placed in a static area, so it is overwritten + * with each call. + * + *----------------------------------------------------------------------- + */ +char * +Targ_FmtTime(time_t tm) +{ + struct tm *parts; + static char buf[128]; + + parts = localtime(&tm); + (void)strftime(buf, sizeof buf, "%k:%M:%S %b %d, %Y", parts); + return(buf); +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintType -- + * Print out a type field giving only those attributes the user can + * set. + * + * Results: + * + * Side Effects: + * + *----------------------------------------------------------------------- + */ +void +Targ_PrintType(int type) +{ + int tbit; + +#define PRINTBIT(attr) case CONCAT(OP_,attr): fprintf(debug_file, "." #attr " "); break +#define PRINTDBIT(attr) case CONCAT(OP_,attr): if (DEBUG(TARG))fprintf(debug_file, "." #attr " "); break + + type &= ~OP_OPMASK; + + while (type) { + tbit = 1 << (ffs(type) - 1); + type &= ~tbit; + + switch(tbit) { + PRINTBIT(OPTIONAL); + PRINTBIT(USE); + PRINTBIT(EXEC); + PRINTBIT(IGNORE); + PRINTBIT(PRECIOUS); + PRINTBIT(SILENT); + PRINTBIT(MAKE); + PRINTBIT(JOIN); + PRINTBIT(INVISIBLE); + PRINTBIT(NOTMAIN); + PRINTDBIT(LIB); + /*XXX: MEMBER is defined, so CONCAT(OP_,MEMBER) gives OP_"%" */ + case OP_MEMBER: if (DEBUG(TARG))fprintf(debug_file, ".MEMBER "); break; + PRINTDBIT(ARCHV); + PRINTDBIT(MADE); + PRINTDBIT(PHONY); + } + } +} + +static const char * +made_name(enum enum_made made) +{ + switch (made) { + case UNMADE: return "unmade"; + case DEFERRED: return "deferred"; + case REQUESTED: return "requested"; + case BEINGMADE: return "being made"; + case MADE: return "made"; + case UPTODATE: return "up-to-date"; + case ERROR: return "error when made"; + case ABORTED: return "aborted"; + default: return "unknown enum_made value"; + } +} + +/*- + *----------------------------------------------------------------------- + * TargPrintNode -- + * print the contents of a node + *----------------------------------------------------------------------- + */ +int +Targ_PrintNode(void *gnp, void *passp) +{ + GNode *gn = (GNode *)gnp; + int pass = passp ? *(int *)passp : 0; + + fprintf(debug_file, "# %s%s, flags %x, type %x, made %d\n", + gn->name, gn->cohort_num, gn->flags, gn->type, gn->made); + if (gn->flags == 0) + return 0; + + if (!OP_NOP(gn->type)) { + fprintf(debug_file, "#\n"); + if (gn == mainTarg) { + fprintf(debug_file, "# *** MAIN TARGET ***\n"); + } + if (pass >= 2) { + if (gn->unmade) { + fprintf(debug_file, "# %d unmade children\n", gn->unmade); + } else { + fprintf(debug_file, "# No unmade children\n"); + } + if (! (gn->type & (OP_JOIN|OP_USE|OP_USEBEFORE|OP_EXEC))) { + if (gn->mtime != 0) { + fprintf(debug_file, "# last modified %s: %s\n", + Targ_FmtTime(gn->mtime), + made_name(gn->made)); + } else if (gn->made != UNMADE) { + fprintf(debug_file, "# non-existent (maybe): %s\n", + made_name(gn->made)); + } else { + fprintf(debug_file, "# unmade\n"); + } + } + if (!Lst_IsEmpty (gn->iParents)) { + fprintf(debug_file, "# implicit parents: "); + Lst_ForEach(gn->iParents, TargPrintName, NULL); + fprintf(debug_file, "\n"); + } + } else { + if (gn->unmade) + fprintf(debug_file, "# %d unmade children\n", gn->unmade); + } + if (!Lst_IsEmpty (gn->parents)) { + fprintf(debug_file, "# parents: "); + Lst_ForEach(gn->parents, TargPrintName, NULL); + fprintf(debug_file, "\n"); + } + if (!Lst_IsEmpty (gn->order_pred)) { + fprintf(debug_file, "# order_pred: "); + Lst_ForEach(gn->order_pred, TargPrintName, NULL); + fprintf(debug_file, "\n"); + } + if (!Lst_IsEmpty (gn->order_succ)) { + fprintf(debug_file, "# order_succ: "); + Lst_ForEach(gn->order_succ, TargPrintName, NULL); + fprintf(debug_file, "\n"); + } + + fprintf(debug_file, "%-16s", gn->name); + switch (gn->type & OP_OPMASK) { + case OP_DEPENDS: + fprintf(debug_file, ": "); break; + case OP_FORCE: + fprintf(debug_file, "! "); break; + case OP_DOUBLEDEP: + fprintf(debug_file, ":: "); break; + } + Targ_PrintType(gn->type); + Lst_ForEach(gn->children, TargPrintName, NULL); + fprintf(debug_file, "\n"); + Lst_ForEach(gn->commands, Targ_PrintCmd, NULL); + fprintf(debug_file, "\n\n"); + if (gn->type & OP_DOUBLEDEP) { + Lst_ForEach(gn->cohorts, Targ_PrintNode, &pass); + } + } + return (0); +} + +/*- + *----------------------------------------------------------------------- + * TargPrintOnlySrc -- + * Print only those targets that are just a source. + * + * Results: + * 0. + * + * Side Effects: + * The name of each file is printed preceded by #\t + * + *----------------------------------------------------------------------- + */ +static int +TargPrintOnlySrc(void *gnp, void *dummy MAKE_ATTR_UNUSED) +{ + GNode *gn = (GNode *)gnp; + if (!OP_NOP(gn->type)) + return 0; + + fprintf(debug_file, "#\t%s [%s] ", + gn->name, gn->path ? gn->path : gn->name); + Targ_PrintType(gn->type); + fprintf(debug_file, "\n"); + + return 0; +} + +/*- + *----------------------------------------------------------------------- + * Targ_PrintGraph -- + * print the entire graph. heh heh + * + * Input: + * pass Which pass this is. 1 => no processing + * 2 => processing done + * + * Results: + * none + * + * Side Effects: + * lots o' output + *----------------------------------------------------------------------- + */ +void +Targ_PrintGraph(int pass) +{ + fprintf(debug_file, "#*** Input graph:\n"); + Lst_ForEach(allTargets, Targ_PrintNode, &pass); + fprintf(debug_file, "\n\n"); + fprintf(debug_file, "#\n# Files that are only sources:\n"); + Lst_ForEach(allTargets, TargPrintOnlySrc, NULL); + fprintf(debug_file, "#*** Global Variables:\n"); + Var_Dump(VAR_GLOBAL); + fprintf(debug_file, "#*** Command-line Variables:\n"); + Var_Dump(VAR_CMD); + fprintf(debug_file, "\n"); + Dir_PrintDirectories(); + fprintf(debug_file, "\n"); + Suff_PrintAll(); +} + +/*- + *----------------------------------------------------------------------- + * TargPropagateNode -- + * Propagate information from a single node to related nodes if + * appropriate. + * + * Input: + * gnp The node that we are processing. + * + * Results: + * Always returns 0, for the benefit of Lst_ForEach(). + * + * Side Effects: + * Information is propagated from this node to cohort or child + * nodes. + * + * If the node was defined with "::", then TargPropagateCohort() + * will be called for each cohort node. + * + * If the node has recursive predecessors, then + * TargPropagateRecpred() will be called for each recursive + * predecessor. + *----------------------------------------------------------------------- + */ +static int +TargPropagateNode(void *gnp, void *junk MAKE_ATTR_UNUSED) +{ + GNode *gn = (GNode *)gnp; + + if (gn->type & OP_DOUBLEDEP) + Lst_ForEach(gn->cohorts, TargPropagateCohort, gnp); + return (0); +} + +/*- + *----------------------------------------------------------------------- + * TargPropagateCohort -- + * Propagate some bits in the type mask from a node to + * a related cohort node. + * + * Input: + * cnp The node that we are processing. + * gnp Another node that has cnp as a cohort. + * + * Results: + * Always returns 0, for the benefit of Lst_ForEach(). + * + * Side Effects: + * cnp's type bitmask is modified to incorporate some of the + * bits from gnp's type bitmask. (XXX need a better explanation.) + *----------------------------------------------------------------------- + */ +static int +TargPropagateCohort(void *cgnp, void *pgnp) +{ + GNode *cgn = (GNode *)cgnp; + GNode *pgn = (GNode *)pgnp; + + cgn->type |= pgn->type & ~OP_OPMASK; + return (0); +} + +/*- + *----------------------------------------------------------------------- + * Targ_Propagate -- + * Propagate information between related nodes. Should be called + * after the makefiles are parsed but before any action is taken. + * + * Results: + * none + * + * Side Effects: + * Information is propagated between related nodes throughout the + * graph. + *----------------------------------------------------------------------- + */ +void +Targ_Propagate(void) +{ + Lst_ForEach(allTargets, TargPropagateNode, NULL); +} diff --git a/usr.bin/make/trace.c b/usr.bin/make/trace.c new file mode 100644 index 0000000..267177f --- /dev/null +++ b/usr.bin/make/trace.c @@ -0,0 +1,116 @@ +/* $NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Bill Sommerfeld + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $"; +#else +#include +#ifndef lint +__RCSID("$NetBSD: trace.c,v 1.11 2008/12/28 18:31:51 christos Exp $"); +#endif /* not lint */ +#endif + +/*- + * trace.c -- + * handle logging of trace events generated by various parts of make. + * + * Interface: + * Trace_Init Initialize tracing (called once during + * the lifetime of the process) + * + * Trace_End Finalize tracing (called before make exits) + * + * Trace_Log Log an event about a particular make job. + */ + +#include + +#include +#include + +#include "make.h" +#include "job.h" +#include "trace.h" + +static FILE *trfile; +static pid_t trpid; +char *trwd; + +static const char *evname[] = { + "BEG", + "END", + "ERR", + "JOB", + "DON", + "INT", +}; + +void +Trace_Init(const char *pathname) +{ + char *p1; + if (pathname != NULL) { + trpid = getpid(); + trwd = Var_Value(".CURDIR", VAR_GLOBAL, &p1); + + trfile = fopen(pathname, "a"); + } +} + +void +Trace_Log(TrEvent event, Job *job) +{ + struct timeval rightnow; + + if (trfile == NULL) + return; + + gettimeofday(&rightnow, NULL); + + fprintf(trfile, "%lld.%06ld %d %s %d %s", + (long long)rightnow.tv_sec, (long)rightnow.tv_usec, + jobTokensRunning, + evname[event], trpid, trwd); + if (job != NULL) { + fprintf(trfile, " %s %d %x %x", job->node->name, + job->pid, job->flags, job->node->type); + } + fputc('\n', trfile); + fflush(trfile); +} + +void +Trace_End(void) +{ + if (trfile != NULL) + fclose(trfile); +} diff --git a/usr.bin/make/trace.h b/usr.bin/make/trace.h new file mode 100644 index 0000000..dc0fc6c --- /dev/null +++ b/usr.bin/make/trace.h @@ -0,0 +1,49 @@ +/* $NetBSD: trace.h,v 1.3 2008/04/28 20:24:14 martin Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Bill Sommerfeld + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * trace.h -- + * Definitions pertaining to the tracing of jobs in parallel mode. + */ + +typedef enum { + MAKESTART, + MAKEEND, + MAKEERROR, + JOBSTART, + JOBEND, + MAKEINTR +} TrEvent; + +void Trace_Init(const char *); +void Trace_Log(TrEvent, Job *); +void Trace_End(void); + diff --git a/usr.bin/make/unit-tests/Makefile b/usr.bin/make/unit-tests/Makefile new file mode 100644 index 0000000..07aaceb --- /dev/null +++ b/usr.bin/make/unit-tests/Makefile @@ -0,0 +1,139 @@ +# $NetBSD: Makefile,v 1.53 2018/05/24 00:25:44 christos Exp $ +# +# Unit tests for make(1) +# The main targets are: +# +# all: run all the tests +# test: run 'all', and compare to expected results +# accept: move generated output to expected results +# +# Adding a test case. +# Each feature should get its own set of tests in its own suitably +# named makefile (*.mk), with its own set of expected results (*.exp), +# and it should be added to the TESTNAMES list. +# + +.MAIN: all + +UNIT_TESTS:= ${.PARSEDIR} +.PATH: ${UNIT_TESTS} + +# Each test is in a sub-makefile. +# Keep the list sorted. +TESTNAMES= \ + comment \ + cond1 \ + cond2 \ + error \ + export \ + export-all \ + export-env \ + doterror \ + dotwait \ + forloop \ + forsubst \ + hash \ + misc \ + moderrs \ + modmatch \ + modmisc \ + modorder \ + modts \ + modword \ + order \ + posix \ + qequals \ + sunshcmd \ + sysv \ + ternary \ + unexport \ + unexport-env \ + varcmd \ + varmisc \ + varquote \ + varshell + +# these tests were broken by referting POSIX chanegs +STRICT_POSIX_TESTS = \ + escape \ + impsrc \ + phony-end \ + posix1 \ + suffixes + +# Override make flags for certain tests +flags.doterror= +flags.order=-j1 + +OUTFILES= ${TESTNAMES:S/$/.out/} + +all: ${OUTFILES} + +CLEANFILES += *.rawout *.out *.status *.tmp *.core *.tmp +CLEANFILES += obj*.[och] lib*.a # posix1.mk +CLEANFILES += issue* .[ab]* # suffixes.mk +CLEANRECURSIVE += dir dummy # posix1.mk + +clean: + rm -f ${CLEANFILES} +.if !empty(CLEANRECURSIVE) + rm -rf ${CLEANRECURSIVE} +.endif + +TEST_MAKE?= ${.MAKE} +TOOL_SED?= sed + +# ensure consistent results from sort(1) +LC_ALL= C +LANG= C +.export LANG LC_ALL + +# the tests are actually done with sub-makes. +.SUFFIXES: .mk .rawout .out +.mk.rawout: + @echo ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} + -@cd ${.OBJDIR} && \ + { ${TEST_MAKE} ${flags.${.TARGET:R}:U-k} -f ${.IMPSRC} \ + 2>&1 ; echo $$? >${.TARGET:R}.status ; } > ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} + +# We always pretend .MAKE was called 'make' +# and strip ${.CURDIR}/ from the output +# and replace anything after 'stopped in' with unit-tests +# so the results can be compared. +.rawout.out: + @echo postprocess ${.TARGET} + @${TOOL_SED} -e 's,^${TEST_MAKE:T:C/\./\\\./g}[][0-9]*:,make:,' \ + -e 's,${TEST_MAKE:C/\./\\\./g},make,' \ + -e '/stopped/s, /.*, unit-tests,' \ + -e 's,${.CURDIR:C/\./\\\./g}/,,g' \ + -e 's,${UNIT_TESTS:C/\./\\\./g}/,,g' \ + < ${.IMPSRC} > ${.TARGET}.tmp + @echo "exit status `cat ${.TARGET:R}.status`" >> ${.TARGET}.tmp + @mv ${.TARGET}.tmp ${.TARGET} + +# Compare all output files +test: ${OUTFILES} .PHONY + @failed= ; \ + for test in ${TESTNAMES}; do \ + diff -u ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || failed="$${failed}$${failed:+ }$${test}" ; \ + done ; \ + if [ -n "$${failed}" ]; then \ + echo "Failed tests: $${failed}" ; false ; \ + else \ + echo "All tests passed" ; \ + fi + +accept: + @for test in ${TESTNAMES}; do \ + cmp -s ${UNIT_TESTS}/$${test}.exp $${test}.out \ + || { echo "Replacing $${test}.exp" ; \ + cp $${test}.out ${UNIT_TESTS}/$${test}.exp ; } \ + done + +.if exists(${TEST_MAKE}) +${TESTNAMES:S/$/.rawout/}: ${TEST_MAKE} +.endif + +.-include diff --git a/usr.bin/make/unit-tests/comment.exp b/usr.bin/make/unit-tests/comment.exp new file mode 100644 index 0000000..9a97df0 --- /dev/null +++ b/usr.bin/make/unit-tests/comment.exp @@ -0,0 +1,5 @@ +comment testing start +this is foo +This is how a comment looks: # comment +comment testing done +exit status 0 diff --git a/usr.bin/make/unit-tests/comment.mk b/usr.bin/make/unit-tests/comment.mk new file mode 100644 index 0000000..7dd7dbb --- /dev/null +++ b/usr.bin/make/unit-tests/comment.mk @@ -0,0 +1,31 @@ +# This is a comment +.if ${MACHINE_ARCH} == something +FOO=bar +.endif + +#\ + Multiline comment + +BAR=# defined +FOOBAR= # defined + +# This is an escaped comment \ +that keeps going until the end of this line + +# Another escaped comment \ +that \ +goes \ +on + +# This is NOT an escaped comment due to the double backslashes \\ +all: hi foo bar + @echo comment testing done + +hi: + @echo comment testing start + +foo: + @echo this is $@ + +bar: + @echo This is how a comment looks: '# comment' diff --git a/usr.bin/make/unit-tests/cond1.exp b/usr.bin/make/unit-tests/cond1.exp new file mode 100644 index 0000000..701d504 --- /dev/null +++ b/usr.bin/make/unit-tests/cond1.exp @@ -0,0 +1,23 @@ +make: "cond1.mk" line 75: warning: extra else +make: "cond1.mk" line 85: warning: extra else +2 is prime +A='other' B='unknown' C='clever' o='no,no' +Passed: + var + ("var") + (var != var) + var != var + !((var != var) && defined(name)) + var == quoted + +1 is not prime +2 is prime +3 is prime +4 is not prime +5 is prime + +make: warning: String comparison operator should be either == or != +make: Bad conditional expression `"0" > 0' in "0" > 0?OK:No + +OK +exit status 0 diff --git a/usr.bin/make/unit-tests/cond1.mk b/usr.bin/make/unit-tests/cond1.mk new file mode 100644 index 0000000..e361832 --- /dev/null +++ b/usr.bin/make/unit-tests/cond1.mk @@ -0,0 +1,109 @@ +# $Id: cond1.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +# hard code these! +TEST_UNAME_S= NetBSD +TEST_UNAME_M= sparc +TEST_MACHINE= i386 + +.if ${TEST_UNAME_S} +Ok=var, +.endif +.if ("${TEST_UNAME_S}") +Ok+=(\"var\"), +.endif +.if (${TEST_UNAME_M} != ${TEST_MACHINE}) +Ok+=(var != var), +.endif +.if ${TEST_UNAME_M} != ${TEST_MACHINE} +Ok+= var != var, +.endif +.if !((${TEST_UNAME_M} != ${TEST_MACHINE}) && defined(X)) +Ok+= !((var != var) && defined(name)), +.endif +# from bsd.obj.mk +MKOBJ?=no +.if ${MKOBJ} == "no" +o= no +Ok+= var == "quoted", +.else +.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) +.if defined(notMAKEOBJDIRPREFIX) +o=${MAKEOBJDIRPREFIX}${__curdir} +.else +o= ${MAKEOBJDIR} +.endif +.endif +o= o +.endif + +# repeat the above to check we get the same result +.if ${MKOBJ} == "no" +o2= no +.else +.if defined(notMAKEOBJDIRPREFIX) || defined(norMAKEOBJDIR) +.if defined(notMAKEOBJDIRPREFIX) +o2=${MAKEOBJDIRPREFIX}${__curdir} +.else +o2= ${MAKEOBJDIR} +.endif +.endif +o2= o +.endif + +PRIMES=2 3 5 7 11 +NUMBERS=1 2 3 4 5 + +n=2 +.if ${PRIMES:M$n} == "" +X=not +.else +X= +.endif + +.if ${MACHINE_ARCH} == no-such +A=one +.else +.if ${MACHINE_ARCH} == not-this +.if ${MACHINE_ARCH} == something-else +A=unlikely +.else +A=no +.endif +.endif +A=other +# We expect an extra else warning - we're not skipping here +.else +A=this should be an error +.endif + +.if $X != "" +.if $X == not +B=one +.else +B=other +# We expect an extra else warning - we are skipping here +.else +B=this should be an error +.endif +.else +B=unknown +.endif + +.if "quoted" == quoted +C=clever +.else +C=dim +.endif + +.if defined(nosuch) && ${nosuch:Mx} != "" +# this should not happen +.info nosuch is x +.endif + +all: + @echo "$n is $X prime" + @echo "A='$A' B='$B' C='$C' o='$o,${o2}'" + @echo "Passed:${.newline} ${Ok:S/,/${.newline}/}" + @echo "${NUMBERS:@n@$n is ${("${PRIMES:M$n}" == ""):?not:} prime${.newline}@}" + @echo "${"${DoNotQuoteHere:U0}" > 0:?OK:No}" + @echo "${${NoSuchNumber:U42} > 0:?OK:No}" diff --git a/usr.bin/make/unit-tests/cond2.exp b/usr.bin/make/unit-tests/cond2.exp new file mode 100644 index 0000000..22e76a5 --- /dev/null +++ b/usr.bin/make/unit-tests/cond2.exp @@ -0,0 +1,7 @@ +make: Bad conditional expression ` == "empty"' in == "empty"?oops:ok +make: "cond2.mk" line 13: Malformed conditional ({TEST_TYPO} == "Ok") +TEST_NOT_SET is empty or not defined +make: "cond2.mk" line 20: Malformed conditional (${TEST_NOT_SET} == "empty") +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +exit status 1 diff --git a/usr.bin/make/unit-tests/cond2.mk b/usr.bin/make/unit-tests/cond2.mk new file mode 100644 index 0000000..aeb5a8c --- /dev/null +++ b/usr.bin/make/unit-tests/cond2.mk @@ -0,0 +1,29 @@ +# $Id: cond2.mk,v 1.2 2015/12/02 00:28:24 sjg Exp $ + +TEST_UNAME_S= NetBSD + +# this should be ok +X:= ${${TEST_UNAME_S} == "NetBSD":?Ok:fail} +.if $X == "Ok" +Y= good +.endif +# expect: Bad conditional expression ` == "empty"' in == "empty"?oops:ok +X:= ${${TEST_NOT_SET} == "empty":?oops:ok} +# expect: Malformed conditional ({TEST_TYPO} == "Ok") +.if {TEST_TYPO} == "Ok" +Y= oops +.endif +.if empty(TEST_NOT_SET) +Y!= echo TEST_NOT_SET is empty or not defined >&2; echo +.endif +# expect: Malformed conditional (${TEST_NOT_SET} == "empty") +.if ${TEST_NOT_SET} == "empty" +Y= oops +.endif + +.if defined(.NDEF) && ${.NDEF} > 0 +Z= yes +.endif + +all: + @echo $@ diff --git a/usr.bin/make/unit-tests/doterror.exp b/usr.bin/make/unit-tests/doterror.exp new file mode 100644 index 0000000..5655644 --- /dev/null +++ b/usr.bin/make/unit-tests/doterror.exp @@ -0,0 +1,9 @@ +At first, I am +happy +and now: sad +*** Error code 1 + +Stop. +make: stopped in unit-tests +.ERROR: Looks like 'sad' is upset. +exit status 1 diff --git a/usr.bin/make/unit-tests/doterror.mk b/usr.bin/make/unit-tests/doterror.mk new file mode 100644 index 0000000..7f1c78f --- /dev/null +++ b/usr.bin/make/unit-tests/doterror.mk @@ -0,0 +1,20 @@ +# $Id: doterror.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + + +.BEGIN: + @echo At first, I am + +.END: + @echo not reached + +.ERROR: + @echo "$@: Looks like '${.ERROR_TARGET}' is upset." + +all: happy sad + +happy: + @echo $@ + +sad: + @echo and now: $@; exit 1 + diff --git a/usr.bin/make/unit-tests/dotwait.exp b/usr.bin/make/unit-tests/dotwait.exp new file mode 100644 index 0000000..bdc0a0e --- /dev/null +++ b/usr.bin/make/unit-tests/dotwait.exp @@ -0,0 +1,30 @@ +simple.1 +simple.1 +simple.2 +simple.2 +recursive.1.1.* +recursive.1.1.* +recursive.1.1.* +recursive.1.1.* +recursive.1.99 +recursive.1.99 +recursive.2.1.* +recursive.2.1.* +recursive.2.1.* +recursive.2.1.* +recursive.2.99 +recursive.2.99 +shared.0 +shared.0 +shared.1.99 +shared.1.99 +shared.2.1 +shared.2.1 +shared.2.99 +shared.2.99 +cycle.1.99 +cycle.1.99 +make: Graph cycles through `cycle.2.99' +make: Graph cycles through `cycle.2.98' +make: Graph cycles through `cycle.2.97' +exit status 0 diff --git a/usr.bin/make/unit-tests/dotwait.mk b/usr.bin/make/unit-tests/dotwait.mk new file mode 100644 index 0000000..bab5993 --- /dev/null +++ b/usr.bin/make/unit-tests/dotwait.mk @@ -0,0 +1,61 @@ +# $NetBSD: dotwait.mk,v 1.2 2017/10/08 20:44:19 sjg Exp $ + +THISMAKEFILE:= ${.PARSEDIR}/${.PARSEFILE} + +TESTS= simple recursive shared cycle +PAUSE= sleep 1 + +# Use a .for loop rather than dependencies here, to ensure +# that the tests are run one by one, with parallelism +# only within tests. +# Ignore "--- target ---" lines printed by parallel make. +all: +.for t in ${TESTS} + @${.MAKE} -f ${THISMAKEFILE} -j4 $t 2>&1 | grep -v "^--- " +.endfor + +# +# Within each test, the names of the sub-targets follow these +# conventions: +# * If it's expected that two or more targets may be made in parallel, +# then the target names will differ only in an alphabetic component +# such as ".a" or ".b". +# * If it's expected that two or more targets should be made in sequence +# then the target names will differ in numeric components, such that +# lexical ordering of the target names matches the expected order +# in which the targets should be made. +# +# Targets may echo ${PARALLEL_TARG} to print a modified version +# of their own name, in which alphabetic components like ".a" or ".b" +# are converted to ".*". Two targets that are expected to +# be made in parallel will thus print the same strings, so that the +# output is independent of the order in which these targets are made. +# +PARALLEL_TARG= ${.TARGET:C/\.[a-z]/.*/g:Q} +.DEFAULT: + @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} +_ECHOUSE: .USE + @echo ${PARALLEL_TARG}; ${PAUSE}; echo ${PARALLEL_TARG} + +# simple: no recursion, no cycles +simple: simple.1 .WAIT simple.2 + +# recursive: all children of the left hand side of the .WAIT +# must be made before any child of the right hand side. +recursive: recursive.1.99 .WAIT recursive.2.99 +recursive.1.99: recursive.1.1.a recursive.1.1.b _ECHOUSE +recursive.2.99: recursive.2.1.a recursive.2.1.b _ECHOUSE + +# shared: both shared.1.99 and shared.2.99 depend on shared.0. +# shared.0 must be made first, even though it is a child of +# the right hand side of the .WAIT. +shared: shared.1.99 .WAIT shared.2.99 +shared.1.99: shared.0 _ECHOUSE +shared.2.99: shared.2.1 shared.0 _ECHOUSE + +# cycle: the cyclic dependency must not cause infinite recursion +# leading to stack overflow and a crash. +cycle: cycle.1.99 .WAIT cycle.2.99 +cycle.2.99: cycle.2.98 _ECHOUSE +cycle.2.98: cycle.2.97 _ECHOUSE +cycle.2.97: cycle.2.99 _ECHOUSE diff --git a/usr.bin/make/unit-tests/error.exp b/usr.bin/make/unit-tests/error.exp new file mode 100644 index 0000000..a2bf71b --- /dev/null +++ b/usr.bin/make/unit-tests/error.exp @@ -0,0 +1,4 @@ +make: "error.mk" line 3: just FYI +make: "error.mk" line 4: warning: this could be serious +make: "error.mk" line 5: this is fatal +exit status 1 diff --git a/usr.bin/make/unit-tests/error.mk b/usr.bin/make/unit-tests/error.mk new file mode 100644 index 0000000..721ed50 --- /dev/null +++ b/usr.bin/make/unit-tests/error.mk @@ -0,0 +1,10 @@ +# $Id: error.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +.info just FYI +.warning this could be serious +.error this is fatal + +all: + +.info.html: + @echo this should be ignored diff --git a/usr.bin/make/unit-tests/escape.exp b/usr.bin/make/unit-tests/escape.exp new file mode 100644 index 0000000..6238e27 --- /dev/null +++ b/usr.bin/make/unit-tests/escape.exp @@ -0,0 +1,104 @@ +var-1bs +printf "%s=:%s:\n" VAR1BS 111\\111; printf "%s=:%s:\n" VAR1BSa 111\\aaa; printf "%s=:%s:\n" VAR1BSA 111\\aaa; printf "%s=:%s:\n" VAR1BSda 111\\\$\{a\}; printf "%s=:%s:\n" VAR1BSdA 111\\\$\{A\}; printf "%s=:%s:\n" VAR1BSc 111\#\ backslash\ escapes\ comment\ char,\ so\ this\ is\ part\ of\ the\ value; printf "%s=:%s:\n" VAR1BSsc 111\\\ ; +VAR1BS=:111\111: +VAR1BSa=:111\aaa: +VAR1BSA=:111\aaa: +VAR1BSda=:111\${a}: +VAR1BSdA=:111\${A}: +VAR1BSc=:111# backslash escapes comment char, so this is part of the value: +VAR1BSsc=:111\ : +var-2bs +printf "%s=:%s:\n" VAR2BS 222\\\\222; printf "%s=:%s:\n" VAR2BSa 222\\\\aaa; printf "%s=:%s:\n" VAR2BSA 222\\\\aaa; printf "%s=:%s:\n" VAR2BSda 222\\\\\$\{a\}; printf "%s=:%s:\n" VAR2BSdA 222\\\\\$\{A\}; printf "%s=:%s:\n" VAR2BSc 222\\\\; printf "%s=:%s:\n" VAR2BSsc 222\\\\; +VAR2BS=:222\\222: +VAR2BSa=:222\\aaa: +VAR2BSA=:222\\aaa: +VAR2BSda=:222\\${a}: +VAR2BSdA=:222\\${A}: +VAR2BSc=:222\\: +VAR2BSsc=:222\\: +var-1bsnl +printf "%s=:%s:\n" VAR1BSNL 111\ 111; printf "%s=:%s:\n" VAR1BSNLa 111\ aaa; printf "%s=:%s:\n" VAR1BSNLA 111\ aaa; printf "%s=:%s:\n" VAR1BSNLda 111\ \$\{a\}; printf "%s=:%s:\n" VAR1BSNLdA 111\ \$\{A\}; printf "%s=:%s:\n" VAR1BSNLc 111; printf "%s=:%s:\n" VAR1BSNLsc 111; +VAR1BSNL=:111 111: +VAR1BSNLa=:111 aaa: +VAR1BSNLA=:111 aaa: +VAR1BSNLda=:111 ${a}: +VAR1BSNLdA=:111 ${A}: +VAR1BSNLc=:111: +VAR1BSNLsc=:111: +var-2bsnl +printf "%s=:%s:\n" VAR2BSNL 222\\\\; printf "%s=:%s:\n" VAR2BSNLa 222\\\\; printf "%s=:%s:\n" VAR2BSNLA 222\\\\; printf "%s=:%s:\n" VAR2BSNLda 222\\\\; printf "%s=:%s:\n" VAR2BSNLdA 222\\\\; printf "%s=:%s:\n" VAR2BSNLc 222\\\\; printf "%s=:%s:\n" VAR2BSNLsc 222\\\\; +VAR2BSNL=:222\\: +VAR2BSNLa=:222\\: +VAR2BSNLA=:222\\: +VAR2BSNLda=:222\\: +VAR2BSNLdA=:222\\: +VAR2BSNLc=:222\\: +VAR2BSNLsc=:222\\: +var-3bsnl +printf "%s=:%s:\n" VAR3BSNL 333\\\\\ 333=; printf "%s=:%s:\n" VAR3BSNLa 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLA 333\\\\\ aaa=; printf "%s=:%s:\n" VAR3BSNLda 333\\\\\ \$\{a\}=; printf "%s=:%s:\n" VAR3BSNLdA 333\\\\\ \$\{A\}=; printf "%s=:%s:\n" VAR3BSNLc 333\\\\; printf "%s=:%s:\n" VAR3BSNLsc 333\\\\; +VAR3BSNL=:333\\ 333=: +VAR3BSNLa=:333\\ aaa=: +VAR3BSNLA=:333\\ aaa=: +VAR3BSNLda=:333\\ ${a}=: +VAR3BSNLdA=:333\\ ${A}=: +VAR3BSNLc=:333\\: +VAR3BSNLsc=:333\\: +var-1bsnl-space +printf "%s=:%s:\n" VAR1BSNL00 first\ line; printf "%s=:%s:\n" VAR1BSNL0 first\ line\ no\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLs first\ line\ one\ space\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLss first\ line\ two\ spaces\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLt first\ line\ one\ tab\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLtt first\ line\ two\ tabs\ on\ second\ line; printf "%s=:%s:\n" VAR1BSNLxx first\ line\ many\ spaces\ and\ tabs\ \[\ \ \ \ \]\ on\ second\ line; +VAR1BSNL00=:first line: +VAR1BSNL0=:first line no space on second line: +VAR1BSNLs=:first line one space on second line: +VAR1BSNLss=:first line two spaces on second line: +VAR1BSNLt=:first line one tab on second line: +VAR1BSNLtt=:first line two tabs on second line: +VAR1BSNLxx=:first line many spaces and tabs [ ] on second line: +cmd-1bsnl +echo :'first line\ +#second line without space\ +third line': +:first line\ +#second line without space\ +third line: +echo :'first line\ + second line spaces should be retained': +:first line\ + second line spaces should be retained: +echo :'first line\ +second line tab should be elided': +:first line\ +second line tab should be elided: +echo :'first line\ + only one tab should be elided, second tab remains' +:first line\ + only one tab should be elided, second tab remains +cmd-1bsnl-eof +echo :'command ending with backslash-newline'; \ + +:command ending with backslash-newline +cmd-2bsnl +echo take one\\ +take one\ +echo take two\\ +take two\ +echo take three\\ +take three\ +cmd-3bsnl +echo :'first line\\\ +#second line without space\\\ +third line': +:first line\\\ +#second line without space\\\ +third line: +echo :'first line\\\ + second line spaces should be retained': +:first line\\\ + second line spaces should be retained: +echo :'first line\\\ +second line tab should be elided': +:first line\\\ +second line tab should be elided: +echo :'first line\\\ + only one tab should be elided, second tab remains' +:first line\\\ + only one tab should be elided, second tab remains +exit status 0 diff --git a/usr.bin/make/unit-tests/escape.mk b/usr.bin/make/unit-tests/escape.mk new file mode 100644 index 0000000..829403d --- /dev/null +++ b/usr.bin/make/unit-tests/escape.mk @@ -0,0 +1,246 @@ +# $Id: escape.mk,v 1.10 2014/09/09 10:22:27 apb Exp $ +# +# Test backslash escaping. + +# Extracts from the POSIX 2008 specification +# : +# +# Comments start with a ( '#' ) and continue until an +# unescaped is reached. +# +# When an escaped (one preceded by a ) is found +# anywhere in the makefile except in a command line, an include +# line, or a line immediately preceding an include line, it shall +# be replaced, along with any leading white space on the following +# line, with a single . +# +# When an escaped is found in a command line in a +# makefile, the command line shall contain the , the +# , and the next line, except that the first character of +# the next line shall not be included if it is a . +# +# When an escaped is found in an include line or in a +# line immediately preceding an include line, the behavior is +# unspecified. +# +# Notice that the behaviour of or +# is not mentioned. I think +# this implies that should be taken literally everywhere +# except before . +# +# Our practice, despite what POSIX might say, is that "\#" +# in a variable assignment stores "#" as part of the value. +# The "\" is not taken literally, and the "#" does not begin a comment. +# +# Also, our practice is that an even number of backslashes before a +# newline in a variable assignment simply stores the backslashes as part +# of the value, and treats the newline as though it was not escaped. +# Similarly, ann even number of backslashes before a newline in a +# command simply uses the backslashes as part of the command test, but +# does not escape the newline. This is compatible with GNU make. + +all: .PHONY +# We will add dependencies like "all: yet-another-test" later. + +# Some variables to be expanded in tests +# +a = aaa +A = ${a} + +# Backslash at end of line in a comment\ +should continue the comment. \ +# This is also tested in comment.mk. + +__printvars: .USE .MADE + @echo ${.TARGET} + ${.ALLSRC:@v@ printf "%s=:%s:\n" ${v:Q} ${${v}:Q}; @} + +# Embedded backslash in variable should be taken literally. +# +VAR1BS = 111\111 +VAR1BSa = 111\${a} +VAR1BSA = 111\${A} +VAR1BSda = 111\$${a} +VAR1BSdA = 111\$${A} +VAR1BSc = 111\# backslash escapes comment char, so this is part of the value +VAR1BSsc = 111\ # This is a comment. Value ends with + +all: var-1bs +var-1bs: .PHONY __printvars VAR1BS VAR1BSa VAR1BSA VAR1BSda VAR1BSdA \ + VAR1BSc VAR1BSsc + +# Double backslash in variable should be taken as two literal backslashes. +# +VAR2BS = 222\\222 +VAR2BSa = 222\\${a} +VAR2BSA = 222\\${A} +VAR2BSda = 222\\$${a} +VAR2BSdA = 222\\$${A} +VAR2BSc = 222\\# backslash does not escape comment char, so this is a comment +VAR2BSsc = 222\\ # This is a comment. Value ends with + +all: var-2bs +var-2bs: .PHONY __printvars VAR2BS VAR2BSa VAR2BSA VAR2BSda VAR2BSdA \ + VAR2BSc VAR2BSsc + +# Backslash-newline in a variable setting is replaced by a single space. +# +VAR1BSNL = 111\ +111 +VAR1BSNLa = 111\ +${a} +VAR1BSNLA = 111\ +${A} +VAR1BSNLda = 111\ +$${a} +VAR1BSNLdA = 111\ +$${A} +VAR1BSNLc = 111\ +# this should be processed as a comment +VAR1BSNLsc = 111\ + # this should be processed as a comment + +all: var-1bsnl +var-1bsnl: .PHONY +var-1bsnl: .PHONY __printvars \ + VAR1BSNL VAR1BSNLa VAR1BSNLA VAR1BSNLda VAR1BSNLdA \ + VAR1BSNLc VAR1BSNLsc + +# Double-backslash-newline in a variable setting. +# Both backslashes should be taken literally, and the newline is NOT escaped. +# +# The second lines below each end with '=' so that they will not +# generate syntax errors regardless of whether or not they are +# treated as part of the value. +# +VAR2BSNL = 222\\ +222= +VAR2BSNLa = 222\\ +${a}= +VAR2BSNLA = 222\\ +${A}= +VAR2BSNLda = 222\\ +$${a}= +VAR2BSNLdA = 222\\ +$${A}= +VAR2BSNLc = 222\\ +# this should be processed as a comment +VAR2BSNLsc = 222\\ + # this should be processed as a comment + +all: var-2bsnl +var-2bsnl: .PHONY __printvars \ + VAR2BSNL VAR2BSNLa VAR2BSNLA VAR2BSNLda VAR2BSNLdA \ + VAR2BSNLc VAR2BSNLsc + +# Triple-backslash-newline in a variable setting. +# First two should be taken literally, and last should escape the newline. +# +# The second lines below each end with '=' so that they will not +# generate syntax errors regardless of whether or not they are +# treated as part of the value. +# +VAR3BSNL = 333\\\ +333= +VAR3BSNLa = 333\\\ +${a}= +VAR3BSNLA = 333\\\ +${A}= +VAR3BSNLda = 333\\\ +$${a}= +VAR3BSNLdA = 333\\\ +$${A}= +VAR3BSNLc = 333\\\ +# this should be processed as a comment +VAR3BSNLsc = 333\\\ + # this should be processed as a comment + +all: var-3bsnl +var-3bsnl: .PHONY __printvars \ + VAR3BSNL VAR3BSNLa VAR3BSNLA VAR3BSNLda VAR3BSNLdA \ + VAR3BSNLc VAR3BSNLsc + +# Backslash-newline in a variable setting, plus any amount of white space +# on the next line, is replaced by a single space. +# +VAR1BSNL00= first line\ + +# above line is entirely empty, and this is a comment +VAR1BSNL0= first line\ +no space on second line +VAR1BSNLs= first line\ + one space on second line +VAR1BSNLss= first line\ + two spaces on second line +VAR1BSNLt= first line\ + one tab on second line +VAR1BSNLtt= first line\ + two tabs on second line +VAR1BSNLxx= first line\ + many spaces and tabs [ ] on second line + +all: var-1bsnl-space +var-1bsnl-space: .PHONY __printvars \ + VAR1BSNL00 VAR1BSNL0 VAR1BSNLs VAR1BSNLss VAR1BSNLt VAR1BSNLtt \ + VAR1BSNLxx + +# Backslash-newline in a command is retained. +# +# The "#" in "# second line without space" makes it a comment instead +# of a syntax error if the preceding line is parsed incorretly. +# The ":" in "third line':" makes it look like the start of a +# target instead of a syntax error if the first line is parsed incorrectly. +# +all: cmd-1bsnl +cmd-1bsnl: .PHONY + @echo ${.TARGET} + echo :'first line\ +#second line without space\ +third line': + echo :'first line\ + second line spaces should be retained': + echo :'first line\ + second line tab should be elided': + echo :'first line\ + only one tab should be elided, second tab remains' + +# When backslash-newline appears at the end of a command script, +# both the backslash and the newline should be passed to the shell. +# The shell should elide the backslash-newline. +# +all: cmd-1bsnl-eof +cmd-1bsnl-eof: + @echo ${.TARGET} + echo :'command ending with backslash-newline'; \ + +# above line must be blank + +# Double-backslash-newline in a command. +# Both backslashes are retained, but the newline is not escaped. +# XXX: This may differ from POSIX, but matches gmake. +# +# When make passes two backslashes to the shell, the shell will pass one +# backslash to the echo commant. +# +all: cmd-2bsnl +cmd-2bsnl: .PHONY + @echo ${.TARGET} + echo take one\\ +# this should be a comment + echo take two\\ + echo take three\\ + +# Triple-backslash-newline in a command is retained. +# +all: cmd-3bsnl +cmd-3bsnl: .PHONY + @echo ${.TARGET} + echo :'first line\\\ +#second line without space\\\ +third line': + echo :'first line\\\ + second line spaces should be retained': + echo :'first line\\\ + second line tab should be elided': + echo :'first line\\\ + only one tab should be elided, second tab remains' diff --git a/usr.bin/make/unit-tests/export-all.exp b/usr.bin/make/unit-tests/export-all.exp new file mode 100644 index 0000000..e3aefd4 --- /dev/null +++ b/usr.bin/make/unit-tests/export-all.exp @@ -0,0 +1,12 @@ +UT_ALL=even this gets exported +UT_BADDIR=unit-tests +UT_DOLLAR=This is $UT_FU +UT_F=fine +UT_FOO=foobar is fubar +UT_FU=fubar +UT_NO=all +UT_OK=good +UT_OKDIR=unit-tests +UT_TEST=export-all +UT_ZOO=hoopie +exit status 0 diff --git a/usr.bin/make/unit-tests/export-all.mk b/usr.bin/make/unit-tests/export-all.mk new file mode 100644 index 0000000..576487b --- /dev/null +++ b/usr.bin/make/unit-tests/export-all.mk @@ -0,0 +1,23 @@ +# $Id: export-all.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $ + +UT_OK=good +UT_F=fine + +# the old way to do :tA +M_tAbad = C,.*,cd & \&\& 'pwd',:sh +# the new +M_tA = tA + +here := ${.PARSEDIR} + +# this will cause trouble (recursing if we let it) +UT_BADDIR = ${${here}/../${here:T}:L:${M_tAbad}:T} +# this will be ok +UT_OKDIR = ${${here}/../${here:T}:L:${M_tA}:T} + +.export + +.include "export.mk" + +UT_TEST=export-all +UT_ALL=even this gets exported diff --git a/usr.bin/make/unit-tests/export-env.exp b/usr.bin/make/unit-tests/export-env.exp new file mode 100644 index 0000000..8a779e6 --- /dev/null +++ b/usr.bin/make/unit-tests/export-env.exp @@ -0,0 +1,11 @@ +make: +UT_TEST=export-env.mk +UT_ENV=not-exported +UT_EXP=not-exported +UT_LIT=literal export-env.mk +env: +UT_TEST=export-env.mk +UT_ENV=exported +UT_EXP=exported +UT_LIT=literal ${UT_TEST} +exit status 0 diff --git a/usr.bin/make/unit-tests/export-env.mk b/usr.bin/make/unit-tests/export-env.mk new file mode 100644 index 0000000..c4d3e75 --- /dev/null +++ b/usr.bin/make/unit-tests/export-env.mk @@ -0,0 +1,27 @@ +# $Id: export-env.mk,v 1.2 2016/02/18 20:25:08 sjg Exp $ + +# our normal .export, subsequent changes affect the environment +UT_TEST=this +.export UT_TEST +UT_TEST:= ${.PARSEFILE} + +# not so with .export-env +UT_ENV=exported +.export-env UT_ENV +UT_ENV=not-exported + +# gmake style export goes further; affects nothing but the environment +UT_EXP=before-export +export UT_EXP=exported +UT_EXP=not-exported + +UT_LIT= literal ${UT_TEST} +.export-literal UT_LIT + +all: + @echo make:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=${$v};@} + @echo env:; ${UT_TEST UT_ENV UT_EXP UT_LIT:L:@v@echo $v=$${$v};@} + + + + diff --git a/usr.bin/make/unit-tests/export.exp b/usr.bin/make/unit-tests/export.exp new file mode 100644 index 0000000..143771c --- /dev/null +++ b/usr.bin/make/unit-tests/export.exp @@ -0,0 +1,6 @@ +UT_DOLLAR=This is $UT_FU +UT_FOO=foobar is fubar +UT_FU=fubar +UT_TEST=export +UT_ZOO=hoopie +exit status 0 diff --git a/usr.bin/make/unit-tests/export.mk b/usr.bin/make/unit-tests/export.mk new file mode 100644 index 0000000..1b4ee72 --- /dev/null +++ b/usr.bin/make/unit-tests/export.mk @@ -0,0 +1,22 @@ +# $Id: export.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +UT_TEST=export +UT_FOO=foo${BAR} +UT_FU=fubar +UT_ZOO=hoopie +UT_NO=all +# belive it or not, we expect this one to come out with $UT_FU unexpanded. +UT_DOLLAR= This is $$UT_FU + +.export UT_FU UT_FOO +.export UT_DOLLAR +# this one will be ignored +.export .MAKE.PID + +BAR=bar is ${UT_FU} + +.MAKE.EXPORTED+= UT_ZOO UT_TEST + +all: + @env | grep '^UT_' | sort + diff --git a/usr.bin/make/unit-tests/forloop.exp b/usr.bin/make/unit-tests/forloop.exp new file mode 100644 index 0000000..df14b75 --- /dev/null +++ b/usr.bin/make/unit-tests/forloop.exp @@ -0,0 +1,19 @@ +x=one +x="two and three" +x=four +x="five" +x=-I/this +x=-I"This or that" +x=-Ithat +x="-DTHIS=\"this and that\"" +cfl=-I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +a=one b="two and three" +a=four b="five" +a=ONE b="TWO AND THREE" +a=FOUR b="FIVE" +We expect an error next: +make: "forloop.mk" line 38: Wrong number of words (9) in .for substitution list with 2 vars +make: Fatal errors encountered -- cannot continue +make: stopped in unit-tests +OK +exit status 0 diff --git a/usr.bin/make/unit-tests/forloop.mk b/usr.bin/make/unit-tests/forloop.mk new file mode 100644 index 0000000..e0399f3 --- /dev/null +++ b/usr.bin/make/unit-tests/forloop.mk @@ -0,0 +1,45 @@ +# $Id: forloop.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +all: for-loop + +LIST = one "two and three" four "five" + +.if make(for-fail) +for-fail: + +XTRA_LIST = xtra +.else + +.for x in ${LIST} +X!= echo 'x=$x' >&2; echo +.endfor + +CFL = -I/this -I"This or that" -Ithat "-DTHIS=\"this and that\"" +cfl= +.for x in ${CFL} +X!= echo 'x=$x' >&2; echo +.if empty(cfl) +cfl= $x +.else +cfl+= $x +.endif +.endfor +X!= echo 'cfl=${cfl}' >&2; echo + +.if ${cfl} != ${CFL} +.error ${.newline}'${cfl}' != ${.newline}'${CFL}' +.endif + +.for a b in ${EMPTY} +X!= echo 'a=$a b=$b' >&2; echo +.endfor +.endif + +.for a b in ${LIST} ${LIST:tu} ${XTRA_LIST} +X!= echo 'a=$a b=$b' >&2; echo +.endfor + +for-loop: + @echo We expect an error next: + @(cd ${.CURDIR} && ${.MAKE} -f ${MAKEFILE} for-fail) && \ + { echo "Oops that should have failed!"; exit 1; } || echo OK diff --git a/usr.bin/make/unit-tests/forsubst.exp b/usr.bin/make/unit-tests/forsubst.exp new file mode 100644 index 0000000..0a98c00 --- /dev/null +++ b/usr.bin/make/unit-tests/forsubst.exp @@ -0,0 +1,2 @@ +.for with :S;... OK +exit status 0 diff --git a/usr.bin/make/unit-tests/forsubst.mk b/usr.bin/make/unit-tests/forsubst.mk new file mode 100644 index 0000000..00cd9b6 --- /dev/null +++ b/usr.bin/make/unit-tests/forsubst.mk @@ -0,0 +1,10 @@ +# $Id: forsubst.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +all: for-subst + +here := ${.PARSEDIR} +# this should not run foul of the parser +.for file in ${.PARSEFILE} +for-subst: ${file:S;^;${here}/;g} + @echo ".for with :S;... OK" +.endfor diff --git a/usr.bin/make/unit-tests/hash.exp b/usr.bin/make/unit-tests/hash.exp new file mode 100644 index 0000000..0a24234 --- /dev/null +++ b/usr.bin/make/unit-tests/hash.exp @@ -0,0 +1,9 @@ +b2af338b +3360ac65 +7747f046 +9ca87054 +880fe816 +208fcbd3 +d5d376eb +de41416c +exit status 0 diff --git a/usr.bin/make/unit-tests/hash.mk b/usr.bin/make/unit-tests/hash.mk new file mode 100644 index 0000000..1ed84e7 --- /dev/null +++ b/usr.bin/make/unit-tests/hash.mk @@ -0,0 +1,18 @@ +STR1= +STR2= a +STR3= ab +STR4= abc +STR5= abcd +STR6= abcde +STR7= abcdef +STR8= abcdefghijklmnopqrstuvwxyz + +all: + @echo ${STR1:hash} + @echo ${STR2:hash} + @echo ${STR3:hash} + @echo ${STR4:hash} + @echo ${STR5:hash} + @echo ${STR6:hash} + @echo ${STR7:hash} + @echo ${STR8:hash} diff --git a/usr.bin/make/unit-tests/impsrc.exp b/usr.bin/make/unit-tests/impsrc.exp new file mode 100644 index 0000000..23e8347 --- /dev/null +++ b/usr.bin/make/unit-tests/impsrc.exp @@ -0,0 +1,13 @@ +expected: source4 +actual: source4 +expected: target1.x +actual: target1.x +expected: target1.y +actual: target1.y +expected: source1 +actual: source1 +expected: source2 +actual: source2 +expected: source1 +actual: source1 +exit status 0 diff --git a/usr.bin/make/unit-tests/impsrc.mk b/usr.bin/make/unit-tests/impsrc.mk new file mode 100644 index 0000000..95ae0c3 --- /dev/null +++ b/usr.bin/make/unit-tests/impsrc.mk @@ -0,0 +1,43 @@ +# $NetBSD: impsrc.mk,v 1.2 2014/08/30 22:21:07 sjg Exp $ + +# Does ${.IMPSRC} work properly? +# It should be set, in order of precedence, to ${.TARGET} of: +# 1) the implied source of a transformation rule, +# 2) the first prerequisite from the dependency line of an explicit rule, or +# 3) the first prerequisite of an explicit rule. +# + +all: target1.z target2 target3 target4 + +.SUFFIXES: .x .y .z + +.x.y: source1 + @echo 'expected: target1.x' + @echo 'actual: $<' + +.y.z: source2 + @echo 'expected: target1.y' + @echo 'actual: $<' + +target1.y: source3 + +target1.x: source4 + @echo 'expected: source4' + @echo 'actual: $<' + +target2: source1 source2 + @echo 'expected: source1' + @echo 'actual: $<' + +target3: source1 +target3: source2 source3 + @echo 'expected: source2' + @echo 'actual: $<' + +target4: source1 +target4: + @echo 'expected: source1' + @echo 'actual: $<' + +source1 source2 source3 source4: + diff --git a/usr.bin/make/unit-tests/misc.exp b/usr.bin/make/unit-tests/misc.exp new file mode 100644 index 0000000..39a9383 --- /dev/null +++ b/usr.bin/make/unit-tests/misc.exp @@ -0,0 +1 @@ +exit status 0 diff --git a/usr.bin/make/unit-tests/misc.mk b/usr.bin/make/unit-tests/misc.mk new file mode 100644 index 0000000..2773e30 --- /dev/null +++ b/usr.bin/make/unit-tests/misc.mk @@ -0,0 +1,16 @@ +# $Id: misc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +.if !exists(${.CURDIR}/) +.warning ${.CURDIR}/ doesn't exist ? +.endif + +.if !exists(${.CURDIR}/.) +.warning ${.CURDIR}/. doesn't exist ? +.endif + +.if !exists(${.CURDIR}/..) +.warning ${.CURDIR}/.. doesn't exist ? +.endif + +all: + @: all is well diff --git a/usr.bin/make/unit-tests/moderrs.exp b/usr.bin/make/unit-tests/moderrs.exp new file mode 100644 index 0000000..cb51aa0 --- /dev/null +++ b/usr.bin/make/unit-tests/moderrs.exp @@ -0,0 +1,16 @@ +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unknown modifier 'Z' +make: Unknown modifier 'Z' +VAR:Z= +Expect: Unclosed variable specification for VAR +make: Unclosed variable specification (expecting '}') for "VAR" (value "Thevariable") modifier S +VAR:S,V,v,=Thevariable +Expect: Unclosed variable specification for VAR +make: Unclosed variable specification after complex modifier (expecting '}') for VAR +VAR:S,V,v,=Thevariable +Expect: Unclosed substitution for VAR (, missing) +make: Unclosed substitution for VAR (, missing) +VAR:S,V,v= +exit status 0 diff --git a/usr.bin/make/unit-tests/moderrs.mk b/usr.bin/make/unit-tests/moderrs.mk new file mode 100644 index 0000000..825e6c7 --- /dev/null +++ b/usr.bin/make/unit-tests/moderrs.mk @@ -0,0 +1,31 @@ +# $Id: moderrs.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# +# various modifier error tests + +VAR=TheVariable +# incase we have to change it ;-) +MOD_UNKN=Z +MOD_TERM=S,V,v +MOD_S:= ${MOD_TERM}, + +all: modunkn modunknV varterm vartermV modtermV + +modunkn: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:Z=${VAR:Z}" + +modunknV: + @echo "Expect: Unknown modifier 'Z'" + @echo "VAR:${MOD_UNKN}=${VAR:${MOD_UNKN}}" + +varterm: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:S,V,v,=${VAR:S,V,v, + +vartermV: + @echo "Expect: Unclosed variable specification for VAR" + @echo VAR:${MOD_TERM},=${VAR:${MOD_S} + +modtermV: + @echo "Expect: Unclosed substitution for VAR (, missing)" + -@echo "VAR:${MOD_TERM}=${VAR:${MOD_TERM}}" diff --git a/usr.bin/make/unit-tests/modmatch.exp b/usr.bin/make/unit-tests/modmatch.exp new file mode 100644 index 0000000..a7bf8b7 --- /dev/null +++ b/usr.bin/make/unit-tests/modmatch.exp @@ -0,0 +1,20 @@ +LIB=a X_LIBS:M${LIB${LIB:tu}} is "/tmp/liba.a" +LIB=a X_LIBS:M*/lib${LIB}.a is "/tmp/liba.a" +LIB=a X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBA.A" +LIB=b X_LIBS:M${LIB${LIB:tu}} is "" +LIB=b X_LIBS:M*/lib${LIB}.a is "" +LIB=b X_LIBS:M*/lib${LIB}.a:tu is "" +LIB=c X_LIBS:M${LIB${LIB:tu}} is "" +LIB=c X_LIBS:M*/lib${LIB}.a is "" +LIB=c X_LIBS:M*/lib${LIB}.a:tu is "" +LIB=d X_LIBS:M${LIB${LIB:tu}} is "/tmp/libd.a" +LIB=d X_LIBS:M*/lib${LIB}.a is "/tmp/libd.a" +LIB=d X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBD.A" +LIB=e X_LIBS:M${LIB${LIB:tu}} is "/tmp/libe.a" +LIB=e X_LIBS:M*/lib${LIB}.a is "/tmp/libe.a" +LIB=e X_LIBS:M*/lib${LIB}.a:tu is "/TMP/LIBE.A" +Mscanner=OK +Upper=One Two Three Four +Lower=five six seven +nose=One Three five +exit status 0 diff --git a/usr.bin/make/unit-tests/modmatch.mk b/usr.bin/make/unit-tests/modmatch.mk new file mode 100644 index 0000000..4519928 --- /dev/null +++ b/usr.bin/make/unit-tests/modmatch.mk @@ -0,0 +1,34 @@ + +X=a b c d e + +.for x in $X +LIB${x:tu}=/tmp/lib$x.a +.endfor + +X_LIBS= ${LIBA} ${LIBD} ${LIBE} + +LIB?=a + +var = head +res = no +.if !empty(var:M${:Uhead\:tail:C/:.*//}) +res = OK +.endif + +all: show-libs check-cclass + +show-libs: + @for x in $X; do ${.MAKE} -f ${MAKEFILE} show LIB=$$x; done + @echo "Mscanner=${res}" + +show: + @echo 'LIB=${LIB} X_LIBS:M$${LIB$${LIB:tu}} is "${X_LIBS:M${LIB${LIB:tu}}}"' + @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a is "${X_LIBS:M*/lib${LIB}.a}"' + @echo 'LIB=${LIB} X_LIBS:M*/lib$${LIB}.a:tu is "${X_LIBS:M*/lib${LIB}.a:tu}"' + +LIST= One Two Three Four five six seven + +check-cclass: + @echo Upper=${LIST:M[A-Z]*} + @echo Lower=${LIST:M[^A-Z]*} + @echo nose=${LIST:M[^s]*[ex]} diff --git a/usr.bin/make/unit-tests/modmisc.exp b/usr.bin/make/unit-tests/modmisc.exp new file mode 100644 index 0000000..e406647 --- /dev/null +++ b/usr.bin/make/unit-tests/modmisc.exp @@ -0,0 +1,10 @@ +path=':/bin:/tmp::/:.:/no/such/dir:.' +path='/bin:/tmp:/:/no/such/dir' +path='/bin:/tmp:/:/no/such/dir' +path='/bin':'/tmp':'/':'/no/such/dir' +path='/bin':'/tmp':'/':'/no/such/dir' +path_/usr/xbin=/opt/xbin/ +paths=/bin /tmp / /no/such/dir /opt/xbin +PATHS=/BIN /TMP / /NO/SUCH/DIR /OPT/XBIN +The answer is 42 +exit status 0 diff --git a/usr.bin/make/unit-tests/modmisc.mk b/usr.bin/make/unit-tests/modmisc.mk new file mode 100644 index 0000000..a292b96 --- /dev/null +++ b/usr.bin/make/unit-tests/modmisc.mk @@ -0,0 +1,38 @@ +# $Id: modmisc.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# +# miscellaneous modifier tests + +# do not put any dirs in this list which exist on some +# but not all target systems - an exists() check is below. +path=:/bin:/tmp::/:.:/no/such/dir:. +# strip cwd from path. +MOD_NODOT=S/:/ /g:N.:ts: +# and decorate, note that $'s need to be doubled. Also note that +# the modifier_variable can be used with other modifiers. +MOD_NODOTX=S/:/ /g:N.:@d@'$$d'@ +# another mod - pretend it is more interesting +MOD_HOMES=S,/home/,/homes/, +MOD_OPT=@d@$${exists($$d):?$$d:$${d:S,/usr,/opt,}}@ +MOD_SEP=S,:, ,g + +all: modvar modvarloop modsysv + +modsysv: + @echo "The answer is ${libfoo.a:L:libfoo.a=42}" + +modvar: + @echo "path='${path}'" + @echo "path='${path:${MOD_NODOT}}'" + @echo "path='${path:S,home,homes,:${MOD_NODOT}}'" + @echo "path=${path:${MOD_NODOTX}:ts:}" + @echo "path=${path:${MOD_HOMES}:${MOD_NODOTX}:ts:}" + +.for d in ${path:${MOD_SEP}:N.} /usr/xbin +path_$d?= ${d:${MOD_OPT}:${MOD_HOMES}}/ +paths+= ${d:${MOD_OPT}:${MOD_HOMES}} +.endfor + +modvarloop: + @echo "path_/usr/xbin=${path_/usr/xbin}" + @echo "paths=${paths}" + @echo "PATHS=${paths:tu}" diff --git a/usr.bin/make/unit-tests/modorder.exp b/usr.bin/make/unit-tests/modorder.exp new file mode 100644 index 0000000..4117427 --- /dev/null +++ b/usr.bin/make/unit-tests/modorder.exp @@ -0,0 +1,11 @@ +LIST = one two three four five six seven eight nine ten +LIST:O = eight five four nine one seven six ten three two +LIST:Ox = Ok +LIST:O:Ox = Ok +LISTX = Ok +LISTSX = Ok +make: Bad modifier `:OX' for LIST +BADMOD 1 = } +make: Bad modifier `:OxXX' for LIST +BADMOD 2 = XX} +exit status 0 diff --git a/usr.bin/make/unit-tests/modorder.mk b/usr.bin/make/unit-tests/modorder.mk new file mode 100644 index 0000000..bc24d33 --- /dev/null +++ b/usr.bin/make/unit-tests/modorder.mk @@ -0,0 +1,22 @@ +# $NetBSD: modorder.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +LIST= one two three four five six seven eight nine ten +LISTX= ${LIST:Ox} +LISTSX:= ${LIST:Ox} +TEST_RESULT= && echo Ok || echo Failed + +# unit-tests have to produce the same results on each run +# so we cannot actually include :Ox output. +all: + @echo "LIST = ${LIST}" + @echo "LIST:O = ${LIST:O}" + # Note that 1 in every 10! trials two independently generated + # randomized orderings will be the same. The test framework doesn't + # support checking probabilistic output, so we accept that the test + # will incorrectly fail with probability 2.8E-7. + @echo "LIST:Ox = `test '${LIST:Ox}' != '${LIST:Ox}' ${TEST_RESULT}`" + @echo "LIST:O:Ox = `test '${LIST:O:Ox}' != '${LIST:O:Ox}' ${TEST_RESULT}`" + @echo "LISTX = `test '${LISTX}' != '${LISTX}' ${TEST_RESULT}`" + @echo "LISTSX = `test '${LISTSX}' = '${LISTSX}' ${TEST_RESULT}`" + @echo "BADMOD 1 = ${LIST:OX}" + @echo "BADMOD 2 = ${LIST:OxXX}" diff --git a/usr.bin/make/unit-tests/modts.exp b/usr.bin/make/unit-tests/modts.exp new file mode 100644 index 0000000..3389649 --- /dev/null +++ b/usr.bin/make/unit-tests/modts.exp @@ -0,0 +1,39 @@ +LIST="one two three four five six" +LIST:ts,="one,two,three,four,five,six" +LIST:ts/:tu="ONE/TWO/THREE/FOUR/FIVE/SIX" +LIST:ts::tu="ONE:TWO:THREE:FOUR:FIVE:SIX" +LIST:ts:tu="ONETWOTHREEFOURFIVESIX" +LIST:tu:ts/="ONE/TWO/THREE/FOUR/FIVE/SIX" +LIST:ts:="one:two:three:four:five:six" +LIST:ts="onetwothreefourfivesix" +LIST:ts:S/two/2/="one2threefourfivesix" +LIST:S/two/2/:ts="one2threefourfivesix" +LIST:ts/:S/two/2/="one/2/three/four/five/six" +Pretend the '/' in '/n' etc. below are back-slashes. +LIST:ts/n="one +two +three +four +five +six" +LIST:ts/t="one two three four five six" +LIST:ts/012:tu="ONE +TWO +THREE +FOUR +FIVE +SIX" +LIST:ts/xa:tu="ONE +TWO +THREE +FOUR +FIVE +SIX" +make: Bad modifier `:tx' for LIST +LIST:tx="}" +make: Bad modifier `:ts\X' for LIST +LIST:ts/x:tu="\X:tu}" +FU_mod-ts="a/b/cool" +FU_mod-ts:ts:T="cool" == cool? +B.${AAA:ts}="Baaa" == Baaa? +exit status 0 diff --git a/usr.bin/make/unit-tests/modts.mk b/usr.bin/make/unit-tests/modts.mk new file mode 100644 index 0000000..254aa42 --- /dev/null +++ b/usr.bin/make/unit-tests/modts.mk @@ -0,0 +1,44 @@ + +LIST= one two three +LIST+= four five six + +FU_mod-ts = a / b / cool + +AAA= a a a +B.aaa= Baaa + +all: mod-ts + +# Use print or printf iff they are builtin. +# XXX note that this causes problems, when make decides +# there is no need to use a shell, so avoid where possible. +.if ${type print 2> /dev/null || echo:L:sh:Mbuiltin} != "" +PRINT= print -r -- +.elif ${type printf 2> /dev/null || echo:L:sh:Mbuiltin} != "" +PRINT= printf '%s\n' +.else +PRINT= echo +.endif + +mod-ts: + @echo 'LIST="${LIST}"' + @echo 'LIST:ts,="${LIST:ts,}"' + @echo 'LIST:ts/:tu="${LIST:ts/:tu}"' + @echo 'LIST:ts::tu="${LIST:ts::tu}"' + @echo 'LIST:ts:tu="${LIST:ts:tu}"' + @echo 'LIST:tu:ts/="${LIST:tu:ts/}"' + @echo 'LIST:ts:="${LIST:ts:}"' + @echo 'LIST:ts="${LIST:ts}"' + @echo 'LIST:ts:S/two/2/="${LIST:ts:S/two/2/}"' + @echo 'LIST:S/two/2/:ts="${LIST:S/two/2/:ts}"' + @echo 'LIST:ts/:S/two/2/="${LIST:ts/:S/two/2/}"' + @echo "Pretend the '/' in '/n' etc. below are back-slashes." + @${PRINT} 'LIST:ts/n="${LIST:ts\n}"' + @${PRINT} 'LIST:ts/t="${LIST:ts\t}"' + @${PRINT} 'LIST:ts/012:tu="${LIST:ts\012:tu}"' + @${PRINT} 'LIST:ts/xa:tu="${LIST:ts\xa:tu}"' + @${PRINT} 'LIST:tx="${LIST:tx}"' + @${PRINT} 'LIST:ts/x:tu="${LIST:ts\X:tu}"' + @${PRINT} 'FU_$@="${FU_${@:ts}:ts}"' + @${PRINT} 'FU_$@:ts:T="${FU_${@:ts}:ts:T}" == cool?' + @${PRINT} 'B.$${AAA:ts}="${B.${AAA:ts}}" == Baaa?' diff --git a/usr.bin/make/unit-tests/modword.exp b/usr.bin/make/unit-tests/modword.exp new file mode 100644 index 0000000..258d7ea --- /dev/null +++ b/usr.bin/make/unit-tests/modword.exp @@ -0,0 +1,122 @@ +make: Bad modifier `:[]' for LIST +LIST:[]="" is an error +LIST:[0]="one two three four five six" +LIST:[0x0]="one two three four five six" +LIST:[000]="one two three four five six" +LIST:[*]="one two three four five six" +LIST:[@]="one two three four five six" +LIST:[0]:C/ /,/="one,two three four five six" +LIST:[0]:C/ /,/g="one,two,three,four,five,six" +LIST:[0]:C/ /,/1g="one,two,three,four,five,six" +LIST:[*]:C/ /,/="one,two three four five six" +LIST:[*]:C/ /,/g="one,two,three,four,five,six" +LIST:[*]:C/ /,/1g="one,two,three,four,five,six" +LIST:[@]:C/ /,/="one two three four five six" +LIST:[@]:C/ /,/g="one two three four five six" +LIST:[@]:C/ /,/1g="one two three four five six" +LIST:[@]:[0]:C/ /,/="one,two three four five six" +LIST:[0]:[@]:C/ /,/="one two three four five six" +LIST:[@]:[*]:C/ /,/="one,two three four five six" +LIST:[*]:[@]:C/ /,/="one two three four five six" +EMPTY="" +EMPTY:[#]="1" == 1 ? +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[#]="1" == 1 ? +REALLYSPACE=" " +REALLYSPACE:[#]="1" == 1 ? +LIST:[#]="6" +LIST:[0]:[#]="1" == 1 ? +LIST:[*]:[#]="1" == 1 ? +LIST:[@]:[#]="6" +LIST:[1]:[#]="1" +LIST:[1..3]:[#]="3" +EMPTY:[1]="" +ESCAPEDSPACE="\ " +ESCAPEDSPACE:[1]="\ " +REALLYSPACE=" " +REALLYSPACE:[1]="" == "" ? +REALLYSPACE:[*]:[1]=" " == " " ? +LIST:[1]="one" +make: Bad modifier `:[1.]' for LIST +LIST:[1.]="" is an error +make: Bad modifier `:[1].' for LIST +LIST:[1].="}" is an error +LIST:[2]="two" +LIST:[6]="six" +LIST:[7]="" +LIST:[999]="" +make: Bad modifier `:[-]' for LIST +LIST:[-]="" is an error +make: Bad modifier `:[--]' for LIST +LIST:[--]="" is an error +LIST:[-1]="six" +LIST:[-2]="five" +LIST:[-6]="one" +LIST:[-7]="" +LIST:[-999]="" +LONGLIST:[17]="17" +LONGLIST:[0x11]="17" +LONGLIST:[021]="17" +LIST:[0]:[1]="one two three four five six" +LIST:[*]:[1]="one two three four five six" +LIST:[@]:[1]="one" +LIST:[0]:[2]="" +LIST:[*]:[2]="" +LIST:[@]:[2]="two" +LIST:[*]:C/ /,/:[2]="" +LIST:[*]:C/ /,/:[*]:[2]="" +LIST:[*]:C/ /,/:[@]:[2]="three" +make: Bad modifier `:[1.]' for LIST +LIST:[1.]="" is an error +make: Bad modifier `:[1..]' for LIST +LIST:[1..]="" is an error +LIST:[1..1]="one" +make: Bad modifier `:[1..1.]' for LIST +LIST:[1..1.]="" is an error +LIST:[1..2]="one two" +LIST:[2..1]="two one" +LIST:[3..-2]="three four five" +LIST:[-4..4]="three four" +make: Bad modifier `:[0..1]' for LIST +LIST:[0..1]="" is an error +make: Bad modifier `:[-1..0]' for LIST +LIST:[-1..0]="" is an error +LIST:[-1..1]="six five four three two one" +LIST:[0..0]="one two three four five six" +LIST:[3..99]="three four five six" +LIST:[-3..-99]="four three two one" +LIST:[-99..-3]="one two three four" +HASH="#" == "#" ? +LIST:[${HASH}]="6" +LIST:[${ZERO}]="one two three four five six" +LIST:[${ZERO}x${ONE}]="one" +LIST:[${ONE}]="one" +LIST:[${MINUSONE}]="six" +LIST:[${STAR}]="one two three four five six" +LIST:[${AT}]="one two three four five six" +make: Bad modifier `:[${EMPTY' for LIST +LIST:[${EMPTY}]="" is an error +LIST:[${LONGLIST:[21]:S/2//}]="one" +LIST:[${LIST:[#]}]="six" +LIST:[${LIST:[${HASH}]}]="six" +LIST:S/ /,/="one two three four five six" +LIST:S/ /,/W="one,two three four five six" +LIST:S/ /,/gW="one,two,three,four,five,six" +EMPTY:S/^/,/="," +EMPTY:S/^/,/W="," +LIST:C/ /,/="one two three four five six" +LIST:C/ /,/W="one,two three four five six" +LIST:C/ /,/gW="one,two,three,four,five,six" +EMPTY:C/^/,/="," +EMPTY:C/^/,/W="," +LIST:tW="one two three four five six" +LIST:tw="one two three four five six" +LIST:tW:C/ /,/="one,two three four five six" +LIST:tW:C/ /,/g="one,two,three,four,five,six" +LIST:tW:C/ /,/1g="one,two,three,four,five,six" +LIST:tw:C/ /,/="one two three four five six" +LIST:tw:C/ /,/g="one two three four five six" +LIST:tw:C/ /,/1g="one two three four five six" +LIST:tw:tW:C/ /,/="one,two three four five six" +LIST:tW:tw:C/ /,/="one two three four five six" +exit status 0 diff --git a/usr.bin/make/unit-tests/modword.mk b/usr.bin/make/unit-tests/modword.mk new file mode 100644 index 0000000..00a56de --- /dev/null +++ b/usr.bin/make/unit-tests/modword.mk @@ -0,0 +1,151 @@ +# $Id: modword.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ +# +# Test behaviour of new :[] modifier + +all: mod-squarebrackets mod-S-W mod-C-W mod-tW-tw + +LIST= one two three +LIST+= four five six +LONGLIST= 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + +EMPTY= # the space should be ignored +ESCAPEDSPACE=\ # escaped space before the '#' +REALLYSPACE:=${EMPTY:C/^/ /W} +HASH= \# +AT= @ +STAR= * +ZERO= 0 +ONE= 1 +MINUSONE= -1 + +mod-squarebrackets: mod-squarebrackets-0-star-at \ + mod-squarebrackets-hash \ + mod-squarebrackets-n \ + mod-squarebrackets-start-end \ + mod-squarebrackets-nested + +mod-squarebrackets-0-star-at: + @echo 'LIST:[]="${LIST:[]}" is an error' + @echo 'LIST:[0]="${LIST:[0]}"' + @echo 'LIST:[0x0]="${LIST:[0x0]}"' + @echo 'LIST:[000]="${LIST:[000]}"' + @echo 'LIST:[*]="${LIST:[*]}"' + @echo 'LIST:[@]="${LIST:[@]}"' + @echo 'LIST:[0]:C/ /,/="${LIST:[0]:C/ /,/}"' + @echo 'LIST:[0]:C/ /,/g="${LIST:[0]:C/ /,/g}"' + @echo 'LIST:[0]:C/ /,/1g="${LIST:[0]:C/ /,/1g}"' + @echo 'LIST:[*]:C/ /,/="${LIST:[*]:C/ /,/}"' + @echo 'LIST:[*]:C/ /,/g="${LIST:[*]:C/ /,/g}"' + @echo 'LIST:[*]:C/ /,/1g="${LIST:[*]:C/ /,/1g}"' + @echo 'LIST:[@]:C/ /,/="${LIST:[@]:C/ /,/}"' + @echo 'LIST:[@]:C/ /,/g="${LIST:[@]:C/ /,/g}"' + @echo 'LIST:[@]:C/ /,/1g="${LIST:[@]:C/ /,/1g}"' + @echo 'LIST:[@]:[0]:C/ /,/="${LIST:[@]:[0]:C/ /,/}"' + @echo 'LIST:[0]:[@]:C/ /,/="${LIST:[0]:[@]:C/ /,/}"' + @echo 'LIST:[@]:[*]:C/ /,/="${LIST:[@]:[*]:C/ /,/}"' + @echo 'LIST:[*]:[@]:C/ /,/="${LIST:[*]:[@]:C/ /,/}"' + +mod-squarebrackets-hash: + @echo 'EMPTY="${EMPTY}"' + @echo 'EMPTY:[#]="${EMPTY:[#]}" == 1 ?' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[#]="${ESCAPEDSPACE:[#]}" == 1 ?' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[#]="${REALLYSPACE:[#]}" == 1 ?' + @echo 'LIST:[#]="${LIST:[#]}"' + @echo 'LIST:[0]:[#]="${LIST:[0]:[#]}" == 1 ?' + @echo 'LIST:[*]:[#]="${LIST:[*]:[#]}" == 1 ?' + @echo 'LIST:[@]:[#]="${LIST:[@]:[#]}"' + @echo 'LIST:[1]:[#]="${LIST:[1]:[#]}"' + @echo 'LIST:[1..3]:[#]="${LIST:[1..3]:[#]}"' + +mod-squarebrackets-n: + @echo 'EMPTY:[1]="${EMPTY:[1]}"' + @echo 'ESCAPEDSPACE="${ESCAPEDSPACE}"' + @echo 'ESCAPEDSPACE:[1]="${ESCAPEDSPACE:[1]}"' + @echo 'REALLYSPACE="${REALLYSPACE}"' + @echo 'REALLYSPACE:[1]="${REALLYSPACE:[1]}" == "" ?' + @echo 'REALLYSPACE:[*]:[1]="${REALLYSPACE:[*]:[1]}" == " " ?' + @echo 'LIST:[1]="${LIST:[1]}"' + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1].="${LIST:[1].}" is an error' + @echo 'LIST:[2]="${LIST:[2]}"' + @echo 'LIST:[6]="${LIST:[6]}"' + @echo 'LIST:[7]="${LIST:[7]}"' + @echo 'LIST:[999]="${LIST:[999]}"' + @echo 'LIST:[-]="${LIST:[-]}" is an error' + @echo 'LIST:[--]="${LIST:[--]}" is an error' + @echo 'LIST:[-1]="${LIST:[-1]}"' + @echo 'LIST:[-2]="${LIST:[-2]}"' + @echo 'LIST:[-6]="${LIST:[-6]}"' + @echo 'LIST:[-7]="${LIST:[-7]}"' + @echo 'LIST:[-999]="${LIST:[-999]}"' + @echo 'LONGLIST:[17]="${LONGLIST:[17]}"' + @echo 'LONGLIST:[0x11]="${LONGLIST:[0x11]}"' + @echo 'LONGLIST:[021]="${LONGLIST:[021]}"' + @echo 'LIST:[0]:[1]="${LIST:[0]:[1]}"' + @echo 'LIST:[*]:[1]="${LIST:[*]:[1]}"' + @echo 'LIST:[@]:[1]="${LIST:[@]:[1]}"' + @echo 'LIST:[0]:[2]="${LIST:[0]:[2]}"' + @echo 'LIST:[*]:[2]="${LIST:[*]:[2]}"' + @echo 'LIST:[@]:[2]="${LIST:[@]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[2]="${LIST:[*]:C/ /,/:[2]}"' + @echo 'LIST:[*]:C/ /,/:[*]:[2]="${LIST:[*]:C/ /,/:[*]:[2]}"' + @echo 'LIST:[*]:C/ /,/:[@]:[2]="${LIST:[*]:C/ /,/:[@]:[2]}"' + +mod-squarebrackets-start-end: + @echo 'LIST:[1.]="${LIST:[1.]}" is an error' + @echo 'LIST:[1..]="${LIST:[1..]}" is an error' + @echo 'LIST:[1..1]="${LIST:[1..1]}"' + @echo 'LIST:[1..1.]="${LIST:[1..1.]}" is an error' + @echo 'LIST:[1..2]="${LIST:[1..2]}"' + @echo 'LIST:[2..1]="${LIST:[2..1]}"' + @echo 'LIST:[3..-2]="${LIST:[3..-2]}"' + @echo 'LIST:[-4..4]="${LIST:[-4..4]}"' + @echo 'LIST:[0..1]="${LIST:[0..1]}" is an error' + @echo 'LIST:[-1..0]="${LIST:[-1..0]}" is an error' + @echo 'LIST:[-1..1]="${LIST:[-1..1]}"' + @echo 'LIST:[0..0]="${LIST:[0..0]}"' + @echo 'LIST:[3..99]="${LIST:[3..99]}"' + @echo 'LIST:[-3..-99]="${LIST:[-3..-99]}"' + @echo 'LIST:[-99..-3]="${LIST:[-99..-3]}"' + +mod-squarebrackets-nested: + @echo 'HASH="${HASH}" == "#" ?' + @echo 'LIST:[$${HASH}]="${LIST:[${HASH}]}"' + @echo 'LIST:[$${ZERO}]="${LIST:[${ZERO}]}"' + @echo 'LIST:[$${ZERO}x$${ONE}]="${LIST:[${ZERO}x${ONE}]}"' + @echo 'LIST:[$${ONE}]="${LIST:[${ONE}]}"' + @echo 'LIST:[$${MINUSONE}]="${LIST:[${MINUSONE}]}"' + @echo 'LIST:[$${STAR}]="${LIST:[${STAR}]}"' + @echo 'LIST:[$${AT}]="${LIST:[${AT}]}"' + @echo 'LIST:[$${EMPTY}]="${LIST:[${EMPTY}]}" is an error' + @echo 'LIST:[$${LONGLIST:[21]:S/2//}]="${LIST:[${LONGLIST:[21]:S/2//}]}"' + @echo 'LIST:[$${LIST:[#]}]="${LIST:[${LIST:[#]}]}"' + @echo 'LIST:[$${LIST:[$${HASH}]}]="${LIST:[${LIST:[${HASH}]}]}"' + +mod-C-W: + @echo 'LIST:C/ /,/="${LIST:C/ /,/}"' + @echo 'LIST:C/ /,/W="${LIST:C/ /,/W}"' + @echo 'LIST:C/ /,/gW="${LIST:C/ /,/gW}"' + @echo 'EMPTY:C/^/,/="${EMPTY:C/^/,/}"' + @echo 'EMPTY:C/^/,/W="${EMPTY:C/^/,/W}"' + +mod-S-W: + @echo 'LIST:S/ /,/="${LIST:S/ /,/}"' + @echo 'LIST:S/ /,/W="${LIST:S/ /,/W}"' + @echo 'LIST:S/ /,/gW="${LIST:S/ /,/gW}"' + @echo 'EMPTY:S/^/,/="${EMPTY:S/^/,/}"' + @echo 'EMPTY:S/^/,/W="${EMPTY:S/^/,/W}"' + +mod-tW-tw: + @echo 'LIST:tW="${LIST:tW}"' + @echo 'LIST:tw="${LIST:tw}"' + @echo 'LIST:tW:C/ /,/="${LIST:tW:C/ /,/}"' + @echo 'LIST:tW:C/ /,/g="${LIST:tW:C/ /,/g}"' + @echo 'LIST:tW:C/ /,/1g="${LIST:tW:C/ /,/1g}"' + @echo 'LIST:tw:C/ /,/="${LIST:tw:C/ /,/}"' + @echo 'LIST:tw:C/ /,/g="${LIST:tw:C/ /,/g}"' + @echo 'LIST:tw:C/ /,/1g="${LIST:tw:C/ /,/1g}"' + @echo 'LIST:tw:tW:C/ /,/="${LIST:tw:tW:C/ /,/}"' + @echo 'LIST:tW:tw:C/ /,/="${LIST:tW:tw:C/ /,/}"' diff --git a/usr.bin/make/unit-tests/order.exp b/usr.bin/make/unit-tests/order.exp new file mode 100644 index 0000000..d876914 --- /dev/null +++ b/usr.bin/make/unit-tests/order.exp @@ -0,0 +1,4 @@ +Making the.c +Making the.h +Making the.o from the.h the.c +exit status 0 diff --git a/usr.bin/make/unit-tests/order.mk b/usr.bin/make/unit-tests/order.mk new file mode 100644 index 0000000..f90b627 --- /dev/null +++ b/usr.bin/make/unit-tests/order.mk @@ -0,0 +1,20 @@ +# $NetBSD: order.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +# Test that .ORDER is handled correctly. +# The explicit dependency the.o: the.h will make us examine the.h +# the .ORDER will prevent us building it immediately, +# we should then examine the.c rather than stop. + +all: the.o + +.ORDER: the.c the.h + +the.c the.h: + @echo Making $@ + +.SUFFIXES: .o .c + +.c.o: + @echo Making $@ from $? + +the.o: the.h diff --git a/usr.bin/make/unit-tests/phony-end.exp b/usr.bin/make/unit-tests/phony-end.exp new file mode 100644 index 0000000..c3c517c --- /dev/null +++ b/usr.bin/make/unit-tests/phony-end.exp @@ -0,0 +1,6 @@ +.TARGET="phony" .PREFIX="phony" .IMPSRC="" +.TARGET="all" .PREFIX="all" .IMPSRC="phony" +.TARGET="ok" .PREFIX="ok" .IMPSRC="" +.TARGET="also.ok" .PREFIX="also.ok" .IMPSRC="" +.TARGET="bug" .PREFIX="bug" .IMPSRC="" +exit status 0 diff --git a/usr.bin/make/unit-tests/phony-end.mk b/usr.bin/make/unit-tests/phony-end.mk new file mode 100644 index 0000000..92cc0e6 --- /dev/null +++ b/usr.bin/make/unit-tests/phony-end.mk @@ -0,0 +1,9 @@ +# $Id: phony-end.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +all ok also.ok bug phony: + @echo '${.TARGET .PREFIX .IMPSRC:L:@v@$v="${$v}"@}' + +.END: ok also.ok bug + +phony bug: .PHONY +all: phony diff --git a/usr.bin/make/unit-tests/posix.exp b/usr.bin/make/unit-tests/posix.exp new file mode 100644 index 0000000..7e74cab --- /dev/null +++ b/usr.bin/make/unit-tests/posix.exp @@ -0,0 +1,23 @@ +Posix says we should execute the command as if run by system(3) +Expect 'Hello,' and 'World!' +Hello, +World! +a command +a command prefixed by '+' executes even with -n +another command +make -n +echo a command +echo "a command prefixed by '+' executes even with -n" +a command prefixed by '+' executes even with -n +echo another command +make -n -j1 +{ echo a command +} || exit $? +echo "a command prefixed by '+' executes even with -n" +a command prefixed by '+' executes even with -n +{ echo another command +} || exit $? +Now we expect an error... +*** Error code 1 (continuing) +`all' not remade because of errors. +exit status 0 diff --git a/usr.bin/make/unit-tests/posix.mk b/usr.bin/make/unit-tests/posix.mk new file mode 100644 index 0000000..a73e2e5 --- /dev/null +++ b/usr.bin/make/unit-tests/posix.mk @@ -0,0 +1,24 @@ +# $Id: posix.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +all: x plus subs err + +x: + @echo "Posix says we should execute the command as if run by system(3)" + @echo "Expect 'Hello,' and 'World!'" + @echo Hello,; false; echo "World!" + +plus: + @echo a command + +@echo "a command prefixed by '+' executes even with -n" + @echo another command + +subs: + @echo make -n + @${.MAKE} -f ${MAKEFILE} -n plus + @echo make -n -j1 + @${.MAKE} -f ${MAKEFILE} -n -j1 plus + +err: + @(echo Now we expect an error...; exit 1) + @echo "Oops! you shouldn't see this!" + diff --git a/usr.bin/make/unit-tests/posix1.exp b/usr.bin/make/unit-tests/posix1.exp new file mode 100644 index 0000000..fa1f15d --- /dev/null +++ b/usr.bin/make/unit-tests/posix1.exp @@ -0,0 +1,186 @@ +${VAR} = "foo bar baz" +a +b +c +foo baR baz, bar baz, foo bar baz, fooadd baradd bazadd +mkdir -p 'dir' +touch 'dir/obj_1.h' +mkdir -p 'dir' +printf '#include "obj_1.h"\nconst char* obj_1 = "dir/obj_1.c";\n' \ + >'dir/obj_1.c' +Local variables + ${@}="dir/obj_1.o" ${<}="dir/obj_1.c" + ${*}="dir/obj_1" ${?}="dir/obj_1.h dir/obj_1.c" + ${%}="" + +Directory and filename parts of local variables + ${@D}="dir" ${@F}="obj_1.o" + ${'obj_2.c' +mkdir -p '.' +touch 'obj_2.h' +Local variables + ${@}="obj2.o" ${<}="obj_2.c" + ${*}="obj2" ${?}="obj_2.c obj_2.h dir/obj_1.h" + ${%}="" + +Directory and filename parts of local variables + ${@D}="." ${@F}="obj2.o" + ${'obj3.c' +Local variables + ${@}="lib.a" ${<}="obj3.c" + ${*}="obj3" ${?}="obj3.h dir/dummy obj3.c" + ${%}="obj3.o" + +Directory and filename parts of local variables + ${@D}="." ${@F}="lib.a" + ${'${@}' + +dir/obj_1.h obj_2.h obj3.h dummy dir/dummy: + mkdir -p '${@D}' + touch '${@}' diff --git a/usr.bin/make/unit-tests/qequals.exp b/usr.bin/make/unit-tests/qequals.exp new file mode 100644 index 0000000..6b2f4dc --- /dev/null +++ b/usr.bin/make/unit-tests/qequals.exp @@ -0,0 +1,2 @@ +V.i386 ?= OK +exit status 0 diff --git a/usr.bin/make/unit-tests/qequals.mk b/usr.bin/make/unit-tests/qequals.mk new file mode 100644 index 0000000..db6d9c3 --- /dev/null +++ b/usr.bin/make/unit-tests/qequals.mk @@ -0,0 +1,8 @@ +# $Id: qequals.mk,v 1.1 2014/08/21 13:44:51 apb Exp $ + +M= i386 +V.i386= OK +V.$M ?= bug + +all: + @echo 'V.$M ?= ${V.$M}' diff --git a/usr.bin/make/unit-tests/suffixes.exp b/usr.bin/make/unit-tests/suffixes.exp new file mode 100644 index 0000000..2a46e1c --- /dev/null +++ b/usr.bin/make/unit-tests/suffixes.exp @@ -0,0 +1,35 @@ +make: don't know how to make issue3 (continuing) +There should be no text after the colon: +touch .a +There should be no text after the colon: +touch .a.b +There should be no text after the colon: +touch .b.a +touch issue5a.c +first set +cp issue5a.c issue5a.d +touch issue5b.d +first set +cp issue5b.d issue5b.c +touch issue5c.d +first set +cp issue5c.d issue5c +touch issue5d.d +first set +cp issue5d.d issue5d.e +touch issue5e.e +first set +cp issue5e.e issue5e.d +make: don't know how to make issue6.f (continuing) +touch issue10.d +first set +cp issue10.d issue10.e +touch issue11.h +touch issue11.first +.ALLSRC: issue11.h issue11.first +cp issue11.h issue11.i +touch issue11.second +.ALLSRC: issue11.i issue11.second +cp issue11.i issue11.j +`all' not remade because of errors. +exit status 0 diff --git a/usr.bin/make/unit-tests/suffixes.mk b/usr.bin/make/unit-tests/suffixes.mk new file mode 100644 index 0000000..113484a --- /dev/null +++ b/usr.bin/make/unit-tests/suffixes.mk @@ -0,0 +1,89 @@ +# $NetBSD: suffixes.mk,v 1.3 2014/08/30 22:21:08 sjg Exp $ + +# Issues from PR 49086 + +# Issue 3: single suffix rules remain active after .SUFFIXES is cleared +# +# There's a rule for issue3.a, but .a is no longer a known suffix when +# targets are being made, so issue3 should not get made. +all: issue3 + +# Issue 4: suffix rules do not become regular rules when .SUFFIXES is cleared +# +# When the rules were encountered, .a and .b were known suffices, but later +# on they were forgotten. These should get created as regular targets. +all: .a .a.b .b.a + +# Issue 5: adding more suffixes does not make existing rules into suffix rules +# +# When the targets .c.d, .d.c, .d, .d.e, and .e.d were encountered, only .a, +# .b and .c were known suffixes, so all of them were regular rules. Later +# rest of the suffixes were made known, so they should all be suffix +# transformation rules. +all: issue5a.d issue5b.c issue5c issue5d.e issue5e.d + +# Issue 6: transformation search can end up in an infinite loop +# +# There is no file or target from which issue6.f could be made from so +# this should fail. The bug was that because rules .e.f, .d.e and .e.d +# exist, make would try to make .f from .e and then infinitely try +# to do .e from .d and vice versa. +all: issue6.f + +# Issue 10: explicit dependencies affect transformation rule selection +# +# If issue10.e is wanted and both issue10.d and issue10.f are available, +# make should choose the .d.e rule, because .d is before .f in .SUFFIXES. +# The bug was that if issue10.d had an explicit dependency on issue10.f, +# it would choose .f.e instead. +all: issue10.e + +# Issue 11: sources from transformation rules are expanded incorrectly +# +# issue11.j should depend on issue11.i and issue11.second and issue11.i +# should depend on issue11.h and issue11.first. The bug was that +# the dynamic sources were expanded before ${.PREFIX} and ${.TARGET} were +# available, so they would have expanded to a null string. +all: issue11.j + +# we need to clean for repeatable results +.BEGIN: clean +clean: + @rm -f issue* .[ab]* + +.SUFFIXES: .a .b .c + +.a .a.b .b.a: + @echo 'There should be no text after the colon: ${.IMPSRC}' + touch ${.TARGET} + +.c.d .d.c .d .d.e .e.d: + @echo 'first set' + cp ${.IMPSRC} ${.TARGET} + +.SUFFIXES: +.SUFFIXES: .c .d .e .f .g + +.e .e.f .f.e: + @echo 'second set' + cp ${.IMPSRC} ${.TARGET} + +issue3.a: + @echo 'There is a bug if you see this.' + touch ${.TARGET} + +issue5a.c issue5b.d issue5c.d issue5d.d issue5e.e issue10.d issue10.f: + touch ${.TARGET} + +.SUFFIXES: .h .i .j + +.h.i: ${.PREFIX}.first + @echo '.ALLSRC: ${.ALLSRC}' + cp ${.IMPSRC} ${.TARGET} + +.i.j: ${.PREFIX}.second + @echo '.ALLSRC: ${.ALLSRC}' + cp ${.IMPSRC} ${.TARGET} + +issue11.h issue11.first issue11.second: + touch ${.TARGET} diff --git a/usr.bin/make/unit-tests/sunshcmd.exp b/usr.bin/make/unit-tests/sunshcmd.exp new file mode 100644 index 0000000..b14f6b6 --- /dev/null +++ b/usr.bin/make/unit-tests/sunshcmd.exp @@ -0,0 +1,4 @@ +TEST1=hello +TEST2=bye +TEST3=later +exit status 0 diff --git a/usr.bin/make/unit-tests/sunshcmd.mk b/usr.bin/make/unit-tests/sunshcmd.mk new file mode 100644 index 0000000..e3baf90 --- /dev/null +++ b/usr.bin/make/unit-tests/sunshcmd.mk @@ -0,0 +1,10 @@ +BYECMD = echo bye +LATERCMD = echo later +TEST1 :sh = echo hello +TEST2 :sh = ${BYECMD} +TEST3 = ${LATERCMD:sh} + +all: + @echo "TEST1=${TEST1}" + @echo "TEST2=${TEST2}" + @echo "TEST3=${TEST3}" diff --git a/usr.bin/make/unit-tests/sysv.exp b/usr.bin/make/unit-tests/sysv.exp new file mode 100644 index 0000000..4cce2de --- /dev/null +++ b/usr.bin/make/unit-tests/sysv.exp @@ -0,0 +1,7 @@ +FOOBAR = +FOOBAR = foobar fubar +fun +fun +fun +In the Sun +exit status 0 diff --git a/usr.bin/make/unit-tests/sysv.mk b/usr.bin/make/unit-tests/sysv.mk new file mode 100644 index 0000000..3651eda --- /dev/null +++ b/usr.bin/make/unit-tests/sysv.mk @@ -0,0 +1,26 @@ +# $Id: sysv.mk,v 1.2 2014/08/30 22:21:08 sjg Exp $ + +FOO ?= +FOOBAR = ${FOO:=bar} + +_this := ${.PARSEDIR}/${.PARSEFILE} + +B = /b +S = / +FUN = ${B}${S}fun +SUN = the Sun + +# we expect nothing when FOO is empty +all: foo fun + +foo: + @echo FOOBAR = ${FOOBAR} +.if empty(FOO) + @FOO="foo fu" ${.MAKE} -f ${_this} foo +.endif + +fun: + @echo ${FUN:T} + @echo ${FUN:${B}${S}fun=fun} + @echo ${FUN:${B}${S}%=%} + @echo ${In:L:%=% ${SUN}} diff --git a/usr.bin/make/unit-tests/ternary.exp b/usr.bin/make/unit-tests/ternary.exp new file mode 100644 index 0000000..ed9c1bd --- /dev/null +++ b/usr.bin/make/unit-tests/ternary.exp @@ -0,0 +1,10 @@ +The answer is unknown +The answer is unknown +The answer is empty +The answer is known +The answer is +The answer is empty +The answer is known +The answer is 42 +The answer is 42 +exit status 0 diff --git a/usr.bin/make/unit-tests/ternary.mk b/usr.bin/make/unit-tests/ternary.mk new file mode 100644 index 0000000..77f8349 --- /dev/null +++ b/usr.bin/make/unit-tests/ternary.mk @@ -0,0 +1,8 @@ + +all: + @for x in "" A= A=42; do ${.MAKE} -f ${MAKEFILE} show $$x; done + +show: + @echo "The answer is ${A:?known:unknown}" + @echo "The answer is ${A:?$A:unknown}" + @echo "The answer is ${empty(A):?empty:$A}" diff --git a/usr.bin/make/unit-tests/unexport-env.exp b/usr.bin/make/unit-tests/unexport-env.exp new file mode 100644 index 0000000..6d43cab --- /dev/null +++ b/usr.bin/make/unit-tests/unexport-env.exp @@ -0,0 +1,2 @@ +UT_TEST=unexport-env +exit status 0 diff --git a/usr.bin/make/unit-tests/unexport-env.mk b/usr.bin/make/unit-tests/unexport-env.mk new file mode 100644 index 0000000..b8192f1 --- /dev/null +++ b/usr.bin/make/unit-tests/unexport-env.mk @@ -0,0 +1,14 @@ +# $Id: unexport-env.mk,v 1.1 2014/08/21 13:44:52 apb Exp $ + +# pick up a bunch of exported vars +.include "export.mk" + +# an example of setting up a minimal environment. +PATH = /bin:/usr/bin:/sbin:/usr/sbin + +# now clobber the environment to just PATH and UT_TEST +UT_TEST = unexport-env + +# this removes everything +.unexport-env +.export PATH UT_TEST diff --git a/usr.bin/make/unit-tests/unexport.exp b/usr.bin/make/unit-tests/unexport.exp new file mode 100644 index 0000000..7b16ea3 --- /dev/null +++ b/usr.bin/make/unit-tests/unexport.exp @@ -0,0 +1,4 @@ +UT_DOLLAR=This is $UT_FU +UT_FU=fubar +UT_TEST=unexport +exit status 0 diff --git a/usr.bin/make/unit-tests/unexport.mk b/usr.bin/make/unit-tests/unexport.mk new file mode 100644 index 0000000..b3d7d34 --- /dev/null +++ b/usr.bin/make/unit-tests/unexport.mk @@ -0,0 +1,8 @@ +# $Id: unexport.mk,v 1.1 2014/08/21 13:44:52 apb Exp $ + +# pick up a bunch of exported vars +.include "export.mk" + +.unexport UT_ZOO UT_FOO + +UT_TEST = unexport diff --git a/usr.bin/make/unit-tests/varcmd.exp b/usr.bin/make/unit-tests/varcmd.exp new file mode 100644 index 0000000..7803c2b --- /dev/null +++ b/usr.bin/make/unit-tests/varcmd.exp @@ -0,0 +1,11 @@ +default FU=fu FOO=foo VAR= +two FU=bar FOO=goo VAR= +immutable FU='bar' +immutable FOO='goo' +three FU=bar FOO=goo VAR= +four FU=bar FOO=goo VAR=Internal +five FU=bar FOO=goo VAR=Internal +five v=is x k=is x +six v=is y k=is y +show-v v=override k=override +exit status 0 diff --git a/usr.bin/make/unit-tests/varcmd.mk b/usr.bin/make/unit-tests/varcmd.mk new file mode 100644 index 0000000..005ed47 --- /dev/null +++ b/usr.bin/make/unit-tests/varcmd.mk @@ -0,0 +1,60 @@ +# $Id: varcmd.mk,v 1.3 2017/12/08 03:36:42 sjg Exp $ +# +# Test behaviour of recursive make and vars set on command line. + +FU=fu +FOO?=foo +.if !empty(.TARGETS) +TAG=${.TARGETS} +.endif +TAG?=default + +all: one + +show: + @echo "${TAG} FU=${FU} FOO=${FOO} VAR=${VAR}" + +one: show + @${.MAKE} -f ${MAKEFILE} FU=bar FOO+=goo two + +two: show + @${.MAKE} -f ${MAKEFILE} three + +three: show + @${.MAKE} -f ${MAKEFILE} four + + +.ifmake two +# this should not work +FU+= oops +FOO+= oops +_FU:= ${FU} +_FOO:= ${FOO} +two: immutable +immutable: + @echo "$@ FU='${_FU}'" + @echo "$@ FOO='${_FOO}'" +.endif +.ifmake four +VAR=Internal +.MAKEOVERRIDES+= VAR +.endif + +four: show + @${.MAKE} -f ${MAKEFILE} five + +M = x +V.y = is y +V.x = is x +V := ${V.$M} +K := ${V} + +show-v: + @echo '${TAG} v=${V} k=${K}' + +five: show show-v + @${.MAKE} -f ${MAKEFILE} M=y six + +six: show-v + @${.MAKE} -f ${MAKEFILE} V=override show-v + diff --git a/usr.bin/make/unit-tests/varmisc.exp b/usr.bin/make/unit-tests/varmisc.exp new file mode 100644 index 0000000..ffe8f8b --- /dev/null +++ b/usr.bin/make/unit-tests/varmisc.exp @@ -0,0 +1,25 @@ + +:D expanded when var set +true +TRUE +:U expanded when var undef +true +TRUE +:D skipped if var undef + +:U skipped when var set +is set +:? only lhs when value true +true +TRUE +:? only rhs when value false +false +FALSE +do not evaluate or expand :? if discarding +is set +year=2016 month=04 day=01 +date=20160401 +Version=123.456.789 == 123456789 +Literal=3.4.5 == 3004005 +We have target specific vars +exit status 0 diff --git a/usr.bin/make/unit-tests/varmisc.mk b/usr.bin/make/unit-tests/varmisc.mk new file mode 100644 index 0000000..34d32cc --- /dev/null +++ b/usr.bin/make/unit-tests/varmisc.mk @@ -0,0 +1,62 @@ +# $Id: varmisc.mk,v 1.8 2017/01/31 18:56:35 sjg Exp $ +# +# Miscellaneous variable tests. + +all: unmatched_var_paren D_true U_true D_false U_false Q_lhs Q_rhs NQ_none \ + strftime cmpv + +unmatched_var_paren: + @echo ${foo::=foo-text} + +True = ${echo true >&2:L:sh}TRUE +False= ${echo false >&2:L:sh}FALSE + +VSET= is set +.undef UNDEF + +U_false: + @echo :U skipped when var set + @echo ${VSET:U${False}} + +D_false: + @echo :D skipped if var undef + @echo ${UNDEF:D${False}} + +U_true: + @echo :U expanded when var undef + @echo ${UNDEF:U${True}} + +D_true: + @echo :D expanded when var set + @echo ${VSET:D${True}} + +Q_lhs: + @echo :? only lhs when value true + @echo ${1:L:?${True}:${False}} + +Q_rhs: + @echo :? only rhs when value false + @echo ${0:L:?${True}:${False}} + +NQ_none: + @echo do not evaluate or expand :? if discarding + @echo ${VSET:U${1:L:?${True}:${False}}} + +April1= 1459494000 + +# slightly contorted syntax to use utc via variable +strftime: + @echo ${year=%Y month=%m day=%d:L:gmtime=1459494000} + @echo date=${%Y%m%d:L:${gmtime=${April1}:L}} + +# big jumps to handle 3 digits per step +M_cmpv.units = 1 1000 1000000 +M_cmpv = S,., ,g:_:range:@i@+ $${_:[-$$i]} \* $${M_cmpv.units:[$$i]}@:S,^,expr 0 ,1:sh + +Version = 123.456.789 +cmpv.only = target specific vars + +cmpv: + @echo Version=${Version} == ${Version:${M_cmpv}} + @echo Literal=3.4.5 == ${3.4.5:L:${M_cmpv}} + @echo We have ${${.TARGET:T}.only} diff --git a/usr.bin/make/unit-tests/varquote.exp b/usr.bin/make/unit-tests/varquote.exp new file mode 100644 index 0000000..63107bf --- /dev/null +++ b/usr.bin/make/unit-tests/varquote.exp @@ -0,0 +1,3 @@ +-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1 +-fdebug-prefix-map=$NETBSDSRCDIR=/usr/src -fdebug-regex-map=/usr/src/(.*)/obj$=/usr/obj/\1 +exit status 0 diff --git a/usr.bin/make/unit-tests/varquote.mk b/usr.bin/make/unit-tests/varquote.mk new file mode 100644 index 0000000..fb8b106 --- /dev/null +++ b/usr.bin/make/unit-tests/varquote.mk @@ -0,0 +1,14 @@ +# $NetBSD: varquote.mk,v 1.4 2018/12/16 18:53:34 christos Exp $ +# +# Test VAR:q modifier + +.if !defined(REPROFLAGS) +REPROFLAGS+= -fdebug-prefix-map=\$$NETBSDSRCDIR=/usr/src +REPROFLAGS+= -fdebug-regex-map='/usr/src/(.*)/obj$$=/usr/obj/\1' +all: + @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:S/\$/&&/g:Q} + @${MAKE} -f ${MAKEFILE} REPROFLAGS=${REPROFLAGS:q} +.else +all: + @printf "%s %s\n" ${REPROFLAGS} +.endif diff --git a/usr.bin/make/unit-tests/varshell.exp b/usr.bin/make/unit-tests/varshell.exp new file mode 100644 index 0000000..6ac8c88 --- /dev/null +++ b/usr.bin/make/unit-tests/varshell.exp @@ -0,0 +1,12 @@ +sh: /bin/no/such/command: not found +make: "varshell.mk" line 5: warning: "/bin/no/such/command" returned non-zero status +make: "varshell.mk" line 6: warning: "kill -14 $$" exited on a signal +make: "varshell.mk" line 7: warning: "false" returned non-zero status +make: "varshell.mk" line 8: warning: "echo "output before the error"; false" returned non-zero status +EXEC_FAILED='' +TERMINATED_BY_SIGNAL='' +ERROR_NO_OUTPUT='' +ERROR_WITH_OUTPUT='output before the error' +NO_ERROR_NO_OUTPUT='' +NO_ERROR_WITH_OUTPUT='this is good' +exit status 0 diff --git a/usr.bin/make/unit-tests/varshell.mk b/usr.bin/make/unit-tests/varshell.mk new file mode 100644 index 0000000..a006736 --- /dev/null +++ b/usr.bin/make/unit-tests/varshell.mk @@ -0,0 +1,18 @@ +# $Id: varshell.mk,v 1.2 2015/04/10 20:41:59 sjg Exp $ +# +# Test VAR != shell command + +EXEC_FAILED != /bin/no/such/command +TERMINATED_BY_SIGNAL != kill -14 $$$$ +ERROR_NO_OUTPUT != false +ERROR_WITH_OUTPUT != echo "output before the error"; false +NO_ERROR_NO_OUTPUT != true +NO_ERROR_WITH_OUTPUT != echo "this is good" + +allvars= EXEC_FAILED TERMINATED_BY_SIGNAL ERROR_NO_OUTPUT ERROR_WITH_OUTPUT \ + NO_ERROR_NO_OUTPUT NO_ERROR_WITH_OUTPUT + +all: +.for v in ${allvars} + @echo ${v}=\'${${v}}\' +.endfor diff --git a/usr.bin/make/util.c b/usr.bin/make/util.c new file mode 100644 index 0000000..506fb4d --- /dev/null +++ b/usr.bin/make/util.c @@ -0,0 +1,494 @@ +/* $NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $ */ + +/* + * Missing stuff from OS's + */ +#if defined(__MINT__) || defined(__linux__) +#include +#endif + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $"; +#else +#include +#ifndef lint +__RCSID("$NetBSD: util.c,v 1.54 2013/11/26 13:44:41 joerg Exp $"); +#endif +#endif + +#include + +#include +#include +#include +#include + +#include "make.h" + +#if !defined(MAKE_NATIVE) && !defined(HAVE_STRERROR) +extern int errno, sys_nerr; +extern char *sys_errlist[]; + +char * +strerror(int e) +{ + static char buf[100]; + if (e < 0 || e >= sys_nerr) { + snprintf(buf, sizeof(buf), "Unknown error %d", e); + return buf; + } + else + return sys_errlist[e]; +} +#endif + +#if !defined(MAKE_NATIVE) && !defined(HAVE_SETENV) +extern char **environ; + +static char * +findenv(const char *name, int *offset) +{ + size_t i, len; + char *p, *q; + + len = strlen(name); + for (i = 0; (q = environ[i]); i++) { + p = strchr(q, '='); + if (p == NULL || p - q != len) + continue; + if (strncmp(name, q, len) == 0) { + *offset = i; + return q + len + 1; + } + } + *offset = i; + return NULL; +} + +char * +getenv(const char *name) +{ + int offset; + + return(findenv(name, &offset)); +} + +int +unsetenv(const char *name) +{ + char **p; + int offset; + + if (name == NULL || *name == '\0' || strchr(name, '=') != NULL) { + errno = EINVAL; + return -1; + } + + while (findenv(name, &offset)) { /* if set multiple times */ + for (p = &environ[offset];; ++p) + if (!(*p = *(p + 1))) + break; + } + return 0; +} + +int +setenv(const char *name, const char *value, int rewrite) +{ + char *c, **newenv; + const char *cc; + size_t l_value, size; + int offset; + + if (name == NULL || value == NULL) { + errno = EINVAL; + return -1; + } + + if (*value == '=') /* no `=' in value */ + ++value; + l_value = strlen(value); + + /* find if already exists */ + if ((c = findenv(name, &offset))) { + if (!rewrite) + return 0; + if (strlen(c) >= l_value) /* old larger; copy over */ + goto copy; + } else { /* create new slot */ + size = sizeof(char *) * (offset + 2); + if (savedEnv == environ) { /* just increase size */ + if ((newenv = realloc(savedEnv, size)) == NULL) + return -1; + savedEnv = newenv; + } else { /* get new space */ + /* + * We don't free here because we don't know if + * the first allocation is valid on all OS's + */ + if ((savedEnv = malloc(size)) == NULL) + return -1; + (void)memcpy(savedEnv, environ, size - sizeof(char *)); + } + environ = savedEnv; + environ[offset + 1] = NULL; + } + for (cc = name; *cc && *cc != '='; ++cc) /* no `=' in name */ + continue; + size = cc - name; + /* name + `=' + value */ + if ((environ[offset] = malloc(size + l_value + 2)) == NULL) + return -1; + c = environ[offset]; + (void)memcpy(c, name, size); + c += size; + *c++ = '='; +copy: + (void)memcpy(c, value, l_value + 1); + return 0; +} + +#ifdef TEST +int +main(int argc, char *argv[]) +{ + setenv(argv[1], argv[2], 0); + printf("%s\n", getenv(argv[1])); + unsetenv(argv[1]); + printf("%s\n", getenv(argv[1])); + return 0; +} +#endif + +#endif + +#if defined(__hpux__) || defined(__hpux) +/* strrcpy(): + * Like strcpy, going backwards and returning the new pointer + */ +static char * +strrcpy(char *ptr, char *str) +{ + int len = strlen(str); + + while (len) + *--ptr = str[--len]; + + return (ptr); +} /* end strrcpy */ + +char *sys_siglist[] = { + "Signal 0", + "Hangup", /* SIGHUP */ + "Interrupt", /* SIGINT */ + "Quit", /* SIGQUIT */ + "Illegal instruction", /* SIGILL */ + "Trace/BPT trap", /* SIGTRAP */ + "IOT trap", /* SIGIOT */ + "EMT trap", /* SIGEMT */ + "Floating point exception", /* SIGFPE */ + "Killed", /* SIGKILL */ + "Bus error", /* SIGBUS */ + "Segmentation fault", /* SIGSEGV */ + "Bad system call", /* SIGSYS */ + "Broken pipe", /* SIGPIPE */ + "Alarm clock", /* SIGALRM */ + "Terminated", /* SIGTERM */ + "User defined signal 1", /* SIGUSR1 */ + "User defined signal 2", /* SIGUSR2 */ + "Child exited", /* SIGCLD */ + "Power-fail restart", /* SIGPWR */ + "Virtual timer expired", /* SIGVTALRM */ + "Profiling timer expired", /* SIGPROF */ + "I/O possible", /* SIGIO */ + "Window size changes", /* SIGWINDOW */ + "Stopped (signal)", /* SIGSTOP */ + "Stopped", /* SIGTSTP */ + "Continued", /* SIGCONT */ + "Stopped (tty input)", /* SIGTTIN */ + "Stopped (tty output)", /* SIGTTOU */ + "Urgent I/O condition", /* SIGURG */ + "Remote lock lost (NFS)", /* SIGLOST */ + "Signal 31", /* reserved */ + "DIL signal" /* SIGDIL */ +}; +#endif /* __hpux__ || __hpux */ + +#if defined(__hpux__) || defined(__hpux) +#include +#include +#include +#include +#include +#include +#include + +int +killpg(int pid, int sig) +{ + return kill(-pid, sig); +} + +#if !defined(__hpux__) && !defined(__hpux) +void +srandom(long seed) +{ + srand48(seed); +} + +long +random(void) +{ + return lrand48(); +} +#endif + +#if !defined(__hpux__) && !defined(__hpux) +int +utimes(char *file, struct timeval tvp[2]) +{ + struct utimbuf t; + + t.actime = tvp[0].tv_sec; + t.modtime = tvp[1].tv_sec; + return(utime(file, &t)); +} +#endif + +#if !defined(BSD) && !defined(d_fileno) +# define d_fileno d_ino +#endif + +#ifndef DEV_DEV_COMPARE +# define DEV_DEV_COMPARE(a, b) ((a) == (b)) +#endif +#define ISDOT(c) ((c)[0] == '.' && (((c)[1] == '\0') || ((c)[1] == '/'))) +#define ISDOTDOT(c) ((c)[0] == '.' && ISDOT(&((c)[1]))) + +char * +getwd(char *pathname) +{ + DIR *dp; + struct dirent *d; + extern int errno; + + struct stat st_root, st_cur, st_next, st_dotdot; + char pathbuf[MAXPATHLEN], nextpathbuf[MAXPATHLEN * 2]; + char *pathptr, *nextpathptr, *cur_name_add; + + /* find the inode of root */ + if (stat("/", &st_root) == -1) { + (void)sprintf(pathname, + "getwd: Cannot stat \"/\" (%s)", strerror(errno)); + return NULL; + } + pathbuf[MAXPATHLEN - 1] = '\0'; + pathptr = &pathbuf[MAXPATHLEN - 1]; + nextpathbuf[MAXPATHLEN - 1] = '\0'; + cur_name_add = nextpathptr = &nextpathbuf[MAXPATHLEN - 1]; + + /* find the inode of the current directory */ + if (lstat(".", &st_cur) == -1) { + (void)sprintf(pathname, + "getwd: Cannot stat \".\" (%s)", strerror(errno)); + return NULL; + } + nextpathptr = strrcpy(nextpathptr, "../"); + + /* Descend to root */ + for (;;) { + + /* look if we found root yet */ + if (st_cur.st_ino == st_root.st_ino && + DEV_DEV_COMPARE(st_cur.st_dev, st_root.st_dev)) { + (void)strcpy(pathname, *pathptr != '/' ? "/" : pathptr); + return (pathname); + } + + /* open the parent directory */ + if (stat(nextpathptr, &st_dotdot) == -1) { + (void)sprintf(pathname, + "getwd: Cannot stat directory \"%s\" (%s)", + nextpathptr, strerror(errno)); + return NULL; + } + if ((dp = opendir(nextpathptr)) == NULL) { + (void)sprintf(pathname, + "getwd: Cannot open directory \"%s\" (%s)", + nextpathptr, strerror(errno)); + return NULL; + } + + /* look in the parent for the entry with the same inode */ + if (DEV_DEV_COMPARE(st_dotdot.st_dev, st_cur.st_dev)) { + /* Parent has same device. No need to stat every member */ + for (d = readdir(dp); d != NULL; d = readdir(dp)) + if (d->d_fileno == st_cur.st_ino) + break; + } + else { + /* + * Parent has a different device. This is a mount point so we + * need to stat every member + */ + for (d = readdir(dp); d != NULL; d = readdir(dp)) { + if (ISDOT(d->d_name) || ISDOTDOT(d->d_name)) + continue; + (void)strcpy(cur_name_add, d->d_name); + if (lstat(nextpathptr, &st_next) == -1) { + (void)sprintf(pathname, + "getwd: Cannot stat \"%s\" (%s)", + d->d_name, strerror(errno)); + (void)closedir(dp); + return NULL; + } + /* check if we found it yet */ + if (st_next.st_ino == st_cur.st_ino && + DEV_DEV_COMPARE(st_next.st_dev, st_cur.st_dev)) + break; + } + } + if (d == NULL) { + (void)sprintf(pathname, + "getwd: Cannot find \".\" in \"..\""); + (void)closedir(dp); + return NULL; + } + st_cur = st_dotdot; + pathptr = strrcpy(pathptr, d->d_name); + pathptr = strrcpy(pathptr, "/"); + nextpathptr = strrcpy(nextpathptr, "../"); + (void)closedir(dp); + *cur_name_add = '\0'; + } +} /* end getwd */ +#endif /* __hpux */ + +/* force posix signals */ +void (* +bmake_signal(int s, void (*a)(int)))(int) +{ + struct sigaction sa, osa; + + sa.sa_handler = a; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(s, &sa, &osa) == -1) + return SIG_ERR; + else + return osa.sa_handler; +} + +#if !defined(MAKE_NATIVE) && !defined(HAVE_VSNPRINTF) +#include + +#if !defined(__osf__) +#ifdef _IOSTRG +#define STRFLAG (_IOSTRG|_IOWRT) /* no _IOWRT: avoid stdio bug */ +#else +#if 0 +#define STRFLAG (_IOREAD) /* XXX: Assume svr4 stdio */ +#endif +#endif /* _IOSTRG */ +#endif /* __osf__ */ + +int +vsnprintf(char *s, size_t n, const char *fmt, va_list args) +{ +#ifdef STRFLAG + FILE fakebuf; + + fakebuf._flag = STRFLAG; + /* + * Some os's are char * _ptr, others are unsigned char *_ptr... + * We cast to void * to make everyone happy. + */ + fakebuf._ptr = (void *)s; + fakebuf._cnt = n-1; + fakebuf._file = -1; + _doprnt(fmt, args, &fakebuf); + fakebuf._cnt++; + putc('\0', &fakebuf); + if (fakebuf._cnt<0) + fakebuf._cnt = 0; + return (n-fakebuf._cnt-1); +#else + (void)vsprintf(s, fmt, args); + return strlen(s); +#endif +} + +int +snprintf(char *s, size_t n, const char *fmt, ...) +{ + va_list ap; + int rv; + + va_start(ap, fmt); + rv = vsnprintf(s, n, fmt, ap); + va_end(ap); + return rv; +} + +#if !defined(MAKE_NATIVE) && !defined(HAVE_STRFTIME) +size_t +strftime(char *buf, size_t len, const char *fmt, const struct tm *tm) +{ + static char months[][4] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + + size_t s; + char *b = buf; + + while (*fmt) { + if (len == 0) + return buf - b; + if (*fmt != '%') { + *buf++ = *fmt++; + len--; + continue; + } + switch (*fmt++) { + case '%': + *buf++ = '%'; + len--; + if (len == 0) return buf - b; + /*FALLTHROUGH*/ + case '\0': + *buf = '%'; + s = 1; + break; + case 'k': + s = snprintf(buf, len, "%d", tm->tm_hour); + break; + case 'M': + s = snprintf(buf, len, "%02d", tm->tm_min); + break; + case 'S': + s = snprintf(buf, len, "%02d", tm->tm_sec); + break; + case 'b': + if (tm->tm_mon >= 12) + return buf - b; + s = snprintf(buf, len, "%s", months[tm->tm_mon]); + break; + case 'd': + s = snprintf(buf, len, "%02d", tm->tm_mday); + break; + case 'Y': + s = snprintf(buf, len, "%d", 1900 + tm->tm_year); + break; + default: + s = snprintf(buf, len, "Unsupported format %c", + fmt[-1]); + break; + } + buf += s; + len -= s; + } +} +#endif +#endif diff --git a/usr.bin/make/var.c b/usr.bin/make/var.c new file mode 100644 index 0000000..547e7fd --- /dev/null +++ b/usr.bin/make/var.c @@ -0,0 +1,4355 @@ +/* $NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $ */ + +/* + * Copyright (c) 1988, 1989, 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 1989 by Berkeley Softworks + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam de Boor. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef MAKE_NATIVE +static char rcsid[] = "$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $"; +#else +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)var.c 8.3 (Berkeley) 3/19/94"; +#else +__RCSID("$NetBSD: var.c,v 1.221 2018/12/21 05:50:19 sjg Exp $"); +#endif +#endif /* not lint */ +#endif + +/*- + * var.c -- + * Variable-handling functions + * + * Interface: + * Var_Set Set the value of a variable in the given + * context. The variable is created if it doesn't + * yet exist. The value and variable name need not + * be preserved. + * + * Var_Append Append more characters to an existing variable + * in the given context. The variable needn't + * exist already -- it will be created if it doesn't. + * A space is placed between the old value and the + * new one. + * + * Var_Exists See if a variable exists. + * + * Var_Value Return the value of a variable in a context or + * NULL if the variable is undefined. + * + * Var_Subst Substitute named variable, or all variables if + * NULL in a string using + * the given context as the top-most one. If the + * third argument is non-zero, Parse_Error is + * called if any variables are undefined. + * + * Var_Parse Parse a variable expansion from a string and + * return the result and the number of characters + * consumed. + * + * Var_Delete Delete a variable in a context. + * + * Var_Init Initialize this module. + * + * Debugging: + * Var_Dump Print out all variables defined in the given + * context. + * + * XXX: There's a lot of duplication in these functions. + */ + +#include +#ifndef NO_REGEX +#include +#include +#endif +#include +#include +#include +#include +#include + +#include "make.h" +#include "buf.h" +#include "dir.h" +#include "job.h" +#include "metachar.h" + +extern int makelevel; +/* + * This lets us tell if we have replaced the original environ + * (which we cannot free). + */ +char **savedEnv = NULL; + +/* + * This is a harmless return value for Var_Parse that can be used by Var_Subst + * to determine if there was an error in parsing -- easier than returning + * a flag, as things outside this module don't give a hoot. + */ +char var_Error[] = ""; + +/* + * Similar to var_Error, but returned when the 'VARF_UNDEFERR' flag for + * Var_Parse is not set. Why not just use a constant? Well, gcc likes + * to condense identical string instances... + */ +static char varNoError[] = ""; + +/* + * Traditionally we consume $$ during := like any other expansion. + * Other make's do not. + * This knob allows controlling the behavior. + * FALSE for old behavior. + * TRUE for new compatible. + */ +#define SAVE_DOLLARS ".MAKE.SAVE_DOLLARS" +static Boolean save_dollars = TRUE; + +/* + * Internally, variables are contained in four different contexts. + * 1) the environment. They may not be changed. If an environment + * variable is appended-to, the result is placed in the global + * context. + * 2) the global context. Variables set in the Makefile are located in + * the global context. It is the penultimate context searched when + * substituting. + * 3) the command-line context. All variables set on the command line + * are placed in this context. They are UNALTERABLE once placed here. + * 4) the local context. Each target has associated with it a context + * list. On this list are located the structures describing such + * local variables as $(@) and $(*) + * The four contexts are searched in the reverse order from which they are + * listed. + */ +GNode *VAR_INTERNAL; /* variables from make itself */ +GNode *VAR_GLOBAL; /* variables from the makefile */ +GNode *VAR_CMD; /* variables defined on the command-line */ + +#define FIND_CMD 0x1 /* look in VAR_CMD when searching */ +#define FIND_GLOBAL 0x2 /* look in VAR_GLOBAL as well */ +#define FIND_ENV 0x4 /* look in the environment also */ + +typedef struct Var { + char *name; /* the variable's name */ + Buffer val; /* its value */ + int flags; /* miscellaneous status flags */ +#define VAR_IN_USE 1 /* Variable's value currently being used. + * Used to avoid recursion */ +#define VAR_FROM_ENV 2 /* Variable comes from the environment */ +#define VAR_JUNK 4 /* Variable is a junk variable that + * should be destroyed when done with + * it. Used by Var_Parse for undefined, + * modified variables */ +#define VAR_KEEP 8 /* Variable is VAR_JUNK, but we found + * a use for it in some modifier and + * the value is therefore valid */ +#define VAR_EXPORTED 16 /* Variable is exported */ +#define VAR_REEXPORT 32 /* Indicate if var needs re-export. + * This would be true if it contains $'s + */ +#define VAR_FROM_CMD 64 /* Variable came from command line */ +} Var; + +/* + * Exporting vars is expensive so skip it if we can + */ +#define VAR_EXPORTED_NONE 0 +#define VAR_EXPORTED_YES 1 +#define VAR_EXPORTED_ALL 2 +static int var_exportedVars = VAR_EXPORTED_NONE; +/* + * We pass this to Var_Export when doing the initial export + * or after updating an exported var. + */ +#define VAR_EXPORT_PARENT 1 +/* + * We pass this to Var_Export1 to tell it to leave the value alone. + */ +#define VAR_EXPORT_LITERAL 2 + +/* Var*Pattern flags */ +#define VAR_SUB_GLOBAL 0x01 /* Apply substitution globally */ +#define VAR_SUB_ONE 0x02 /* Apply substitution to one word */ +#define VAR_SUB_MATCHED 0x04 /* There was a match */ +#define VAR_MATCH_START 0x08 /* Match at start of word */ +#define VAR_MATCH_END 0x10 /* Match at end of word */ +#define VAR_NOSUBST 0x20 /* don't expand vars in VarGetPattern */ + +/* Var_Set flags */ +#define VAR_NO_EXPORT 0x01 /* do not export */ + +typedef struct { + /* + * The following fields are set by Var_Parse() when it + * encounters modifiers that need to keep state for use by + * subsequent modifiers within the same variable expansion. + */ + Byte varSpace; /* Word separator in expansions */ + Boolean oneBigWord; /* TRUE if we will treat the variable as a + * single big word, even if it contains + * embedded spaces (as opposed to the + * usual behaviour of treating it as + * several space-separated words). */ +} Var_Parse_State; + +/* struct passed as 'void *' to VarSubstitute() for ":S/lhs/rhs/", + * to VarSYSVMatch() for ":lhs=rhs". */ +typedef struct { + const char *lhs; /* String to match */ + int leftLen; /* Length of string */ + const char *rhs; /* Replacement string (w/ &'s removed) */ + int rightLen; /* Length of replacement */ + int flags; +} VarPattern; + +/* struct passed as 'void *' to VarLoopExpand() for ":@tvar@str@" */ +typedef struct { + GNode *ctxt; /* variable context */ + char *tvar; /* name of temp var */ + int tvarLen; + char *str; /* string to expand */ + int strLen; + int errnum; /* errnum for not defined */ +} VarLoop_t; + +#ifndef NO_REGEX +/* struct passed as 'void *' to VarRESubstitute() for ":C///" */ +typedef struct { + regex_t re; + int nsub; + regmatch_t *matches; + char *replace; + int flags; +} VarREPattern; +#endif + +/* struct passed to VarSelectWords() for ":[start..end]" */ +typedef struct { + int start; /* first word to select */ + int end; /* last word to select */ +} VarSelectWords_t; + +static Var *VarFind(const char *, GNode *, int); +static void VarAdd(const char *, const char *, GNode *); +static Boolean VarHead(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static Boolean VarTail(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static Boolean VarSuffix(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static Boolean VarRoot(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static Boolean VarMatch(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +#ifdef SYSVVARSUB +static Boolean VarSYSVMatch(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +#endif +static Boolean VarNoMatch(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +#ifndef NO_REGEX +static void VarREError(int, regex_t *, const char *); +static Boolean VarRESubstitute(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +#endif +static Boolean VarSubstitute(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static Boolean VarLoopExpand(GNode *, Var_Parse_State *, + char *, Boolean, Buffer *, void *); +static char *VarGetPattern(GNode *, Var_Parse_State *, + int, const char **, int, int *, int *, + VarPattern *); +static char *VarQuote(char *, Boolean); +static char *VarHash(char *); +static char *VarModify(GNode *, Var_Parse_State *, + const char *, + Boolean (*)(GNode *, Var_Parse_State *, char *, Boolean, Buffer *, void *), + void *); +static char *VarOrder(const char *, const char); +static char *VarUniq(const char *); +static int VarWordCompare(const void *, const void *); +static void VarPrintVar(void *); + +#define BROPEN '{' +#define BRCLOSE '}' +#define PROPEN '(' +#define PRCLOSE ')' + +/*- + *----------------------------------------------------------------------- + * VarFind -- + * Find the given variable in the given context and any other contexts + * indicated. + * + * Input: + * name name to find + * ctxt context in which to find it + * flags FIND_GLOBAL set means to look in the + * VAR_GLOBAL context as well. FIND_CMD set means + * to look in the VAR_CMD context also. FIND_ENV + * set means to look in the environment + * + * Results: + * A pointer to the structure describing the desired variable or + * NULL if the variable does not exist. + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +static Var * +VarFind(const char *name, GNode *ctxt, int flags) +{ + Hash_Entry *var; + Var *v; + + /* + * If the variable name begins with a '.', it could very well be one of + * the local ones. We check the name against all the local variables + * and substitute the short version in for 'name' if it matches one of + * them. + */ + if (*name == '.' && isupper((unsigned char) name[1])) + switch (name[1]) { + case 'A': + if (!strcmp(name, ".ALLSRC")) + name = ALLSRC; + if (!strcmp(name, ".ARCHIVE")) + name = ARCHIVE; + break; + case 'I': + if (!strcmp(name, ".IMPSRC")) + name = IMPSRC; + break; + case 'M': + if (!strcmp(name, ".MEMBER")) + name = MEMBER; + break; + case 'O': + if (!strcmp(name, ".OODATE")) + name = OODATE; + break; + case 'P': + if (!strcmp(name, ".PREFIX")) + name = PREFIX; + break; + case 'T': + if (!strcmp(name, ".TARGET")) + name = TARGET; + break; + } +#ifdef notyet + /* for compatibility with gmake */ + if (name[0] == '^' && name[1] == '\0') + name = ALLSRC; +#endif + + /* + * First look for the variable in the given context. If it's not there, + * look for it in VAR_CMD, VAR_GLOBAL and the environment, in that order, + * depending on the FIND_* flags in 'flags' + */ + var = Hash_FindEntry(&ctxt->context, name); + + if ((var == NULL) && (flags & FIND_CMD) && (ctxt != VAR_CMD)) { + var = Hash_FindEntry(&VAR_CMD->context, name); + } + if (!checkEnvFirst && (var == NULL) && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Hash_FindEntry(&VAR_GLOBAL->context, name); + if ((var == NULL) && (ctxt != VAR_INTERNAL)) { + /* VAR_INTERNAL is subordinate to VAR_GLOBAL */ + var = Hash_FindEntry(&VAR_INTERNAL->context, name); + } + } + if ((var == NULL) && (flags & FIND_ENV)) { + char *env; + + if ((env = getenv(name)) != NULL) { + int len; + + v = bmake_malloc(sizeof(Var)); + v->name = bmake_strdup(name); + + len = strlen(env); + + Buf_Init(&v->val, len + 1); + Buf_AddBytes(&v->val, len, env); + + v->flags = VAR_FROM_ENV; + return (v); + } else if (checkEnvFirst && (flags & FIND_GLOBAL) && + (ctxt != VAR_GLOBAL)) + { + var = Hash_FindEntry(&VAR_GLOBAL->context, name); + if ((var == NULL) && (ctxt != VAR_INTERNAL)) { + var = Hash_FindEntry(&VAR_INTERNAL->context, name); + } + if (var == NULL) { + return NULL; + } else { + return ((Var *)Hash_GetValue(var)); + } + } else { + return NULL; + } + } else if (var == NULL) { + return NULL; + } else { + return ((Var *)Hash_GetValue(var)); + } +} + +/*- + *----------------------------------------------------------------------- + * VarFreeEnv -- + * If the variable is an environment variable, free it + * + * Input: + * v the variable + * destroy true if the value buffer should be destroyed. + * + * Results: + * 1 if it is an environment variable 0 ow. + * + * Side Effects: + * The variable is free'ed if it is an environent variable. + *----------------------------------------------------------------------- + */ +static Boolean +VarFreeEnv(Var *v, Boolean destroy) +{ + if ((v->flags & VAR_FROM_ENV) == 0) + return FALSE; + free(v->name); + Buf_Destroy(&v->val, destroy); + free(v); + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * VarAdd -- + * Add a new variable of name name and value val to the given context + * + * Input: + * name name of variable to add + * val value to set it to + * ctxt context in which to set it + * + * Results: + * None + * + * Side Effects: + * The new variable is placed at the front of the given context + * The name and val arguments are duplicated so they may + * safely be freed. + *----------------------------------------------------------------------- + */ +static void +VarAdd(const char *name, const char *val, GNode *ctxt) +{ + Var *v; + int len; + Hash_Entry *h; + + v = bmake_malloc(sizeof(Var)); + + len = val ? strlen(val) : 0; + Buf_Init(&v->val, len+1); + Buf_AddBytes(&v->val, len, val); + + v->flags = 0; + + h = Hash_CreateEntry(&ctxt->context, name, NULL); + Hash_SetValue(h, v); + v->name = h->name; + if (DEBUG(VAR) && (ctxt->flags & INTERNAL) == 0) { + fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val); + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Delete -- + * Remove a variable from a context. + * + * Results: + * None. + * + * Side Effects: + * The Var structure is removed and freed. + * + *----------------------------------------------------------------------- + */ +void +Var_Delete(const char *name, GNode *ctxt) +{ + Hash_Entry *ln; + char *cp; + + if (strchr(name, '$')) { + cp = Var_Subst(NULL, name, VAR_GLOBAL, VARF_WANTRES); + } else { + cp = (char *)name; + } + ln = Hash_FindEntry(&ctxt->context, cp); + if (DEBUG(VAR)) { + fprintf(debug_file, "%s:delete %s%s\n", + ctxt->name, cp, ln ? "" : " (not found)"); + } + if (cp != name) { + free(cp); + } + if (ln != NULL) { + Var *v; + + v = (Var *)Hash_GetValue(ln); + if ((v->flags & VAR_EXPORTED)) { + unsetenv(v->name); + } + if (strcmp(MAKE_EXPORTED, v->name) == 0) { + var_exportedVars = VAR_EXPORTED_NONE; + } + if (v->name != ln->name) + free(v->name); + Hash_DeleteEntry(&ctxt->context, ln); + Buf_Destroy(&v->val, TRUE); + free(v); + } +} + + +/* + * Export a var. + * We ignore make internal variables (those which start with '.') + * Also we jump through some hoops to avoid calling setenv + * more than necessary since it can leak. + * We only manipulate flags of vars if 'parent' is set. + */ +static int +Var_Export1(const char *name, int flags) +{ + char tmp[BUFSIZ]; + Var *v; + char *val = NULL; + int n; + int parent = (flags & VAR_EXPORT_PARENT); + + if (*name == '.') + return 0; /* skip internals */ + if (!name[1]) { + /* + * A single char. + * If it is one of the vars that should only appear in + * local context, skip it, else we can get Var_Subst + * into a loop. + */ + switch (name[0]) { + case '@': + case '%': + case '*': + case '!': + return 0; + } + } + v = VarFind(name, VAR_GLOBAL, 0); + if (v == NULL) { + return 0; + } + if (!parent && + (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) { + return 0; /* nothing to do */ + } + val = Buf_GetAll(&v->val, NULL); + if ((flags & VAR_EXPORT_LITERAL) == 0 && strchr(val, '$')) { + if (parent) { + /* + * Flag this as something we need to re-export. + * No point actually exporting it now though, + * the child can do it at the last minute. + */ + v->flags |= (VAR_EXPORTED|VAR_REEXPORT); + return 1; + } + if (v->flags & VAR_IN_USE) { + /* + * We recursed while exporting in a child. + * This isn't going to end well, just skip it. + */ + return 0; + } + n = snprintf(tmp, sizeof(tmp), "${%s}", name); + if (n < (int)sizeof(tmp)) { + val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + setenv(name, val, 1); + free(val); + } + } else { + if (parent) { + v->flags &= ~VAR_REEXPORT; /* once will do */ + } + if (parent || !(v->flags & VAR_EXPORTED)) { + setenv(name, val, 1); + } + } + /* + * This is so Var_Set knows to call Var_Export again... + */ + if (parent) { + v->flags |= VAR_EXPORTED; + } + return 1; +} + +/* + * This gets called from our children. + */ +void +Var_ExportVars(void) +{ + char tmp[BUFSIZ]; + Hash_Entry *var; + Hash_Search state; + Var *v; + char *val; + int n; + + /* + * Several make's support this sort of mechanism for tracking + * recursion - but each uses a different name. + * We allow the makefiles to update MAKELEVEL and ensure + * children see a correctly incremented value. + */ + snprintf(tmp, sizeof(tmp), "%d", makelevel + 1); + setenv(MAKE_LEVEL_ENV, tmp, 1); + + if (VAR_EXPORTED_NONE == var_exportedVars) + return; + + if (VAR_EXPORTED_ALL == var_exportedVars) { + /* + * Ouch! This is crazy... + */ + for (var = Hash_EnumFirst(&VAR_GLOBAL->context, &state); + var != NULL; + var = Hash_EnumNext(&state)) { + v = (Var *)Hash_GetValue(var); + Var_Export1(v->name, 0); + } + return; + } + /* + * We have a number of exported vars, + */ + n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}"); + if (n < (int)sizeof(tmp)) { + char **av; + char *as; + int ac; + int i; + + val = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + if (*val) { + av = brk_string(val, &ac, FALSE, &as); + for (i = 0; i < ac; i++) { + Var_Export1(av[i], 0); + } + free(as); + free(av); + } + free(val); + } +} + +/* + * This is called when .export is seen or + * .MAKE.EXPORTED is modified. + * It is also called when any exported var is modified. + */ +void +Var_Export(char *str, int isExport) +{ + char *name; + char *val; + char **av; + char *as; + int flags; + int ac; + int i; + + if (isExport && (!str || !str[0])) { + var_exportedVars = VAR_EXPORTED_ALL; /* use with caution! */ + return; + } + + flags = 0; + if (strncmp(str, "-env", 4) == 0) { + str += 4; + } else if (strncmp(str, "-literal", 8) == 0) { + str += 8; + flags |= VAR_EXPORT_LITERAL; + } else { + flags |= VAR_EXPORT_PARENT; + } + val = Var_Subst(NULL, str, VAR_GLOBAL, VARF_WANTRES); + if (*val) { + av = brk_string(val, &ac, FALSE, &as); + for (i = 0; i < ac; i++) { + name = av[i]; + if (!name[1]) { + /* + * A single char. + * If it is one of the vars that should only appear in + * local context, skip it, else we can get Var_Subst + * into a loop. + */ + switch (name[0]) { + case '@': + case '%': + case '*': + case '!': + continue; + } + } + if (Var_Export1(name, flags)) { + if (VAR_EXPORTED_ALL != var_exportedVars) + var_exportedVars = VAR_EXPORTED_YES; + if (isExport && (flags & VAR_EXPORT_PARENT)) { + Var_Append(MAKE_EXPORTED, name, VAR_GLOBAL); + } + } + } + free(as); + free(av); + } + free(val); +} + + +/* + * This is called when .unexport[-env] is seen. + */ +extern char **environ; + +void +Var_UnExport(char *str) +{ + char tmp[BUFSIZ]; + char *vlist; + char *cp; + Boolean unexport_env; + int n; + + if (!str || !str[0]) { + return; /* assert? */ + } + + vlist = NULL; + + str += 8; + unexport_env = (strncmp(str, "-env", 4) == 0); + if (unexport_env) { + char **newenv; + + cp = getenv(MAKE_LEVEL_ENV); /* we should preserve this */ + if (environ == savedEnv) { + /* we have been here before! */ + newenv = bmake_realloc(environ, 2 * sizeof(char *)); + } else { + if (savedEnv) { + free(savedEnv); + savedEnv = NULL; + } + newenv = bmake_malloc(2 * sizeof(char *)); + } + if (!newenv) + return; + /* Note: we cannot safely free() the original environ. */ + environ = savedEnv = newenv; + newenv[0] = NULL; + newenv[1] = NULL; + if (cp && *cp) + setenv(MAKE_LEVEL_ENV, cp, 1); + } else { + for (; *str != '\n' && isspace((unsigned char) *str); str++) + continue; + if (str[0] && str[0] != '\n') { + vlist = str; + } + } + + if (!vlist) { + /* Using .MAKE.EXPORTED */ + n = snprintf(tmp, sizeof(tmp), "${" MAKE_EXPORTED ":O:u}"); + if (n < (int)sizeof(tmp)) { + vlist = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + } + } + if (vlist) { + Var *v; + char **av; + char *as; + int ac; + int i; + + av = brk_string(vlist, &ac, FALSE, &as); + for (i = 0; i < ac; i++) { + v = VarFind(av[i], VAR_GLOBAL, 0); + if (!v) + continue; + if (!unexport_env && + (v->flags & (VAR_EXPORTED|VAR_REEXPORT)) == VAR_EXPORTED) { + unsetenv(v->name); + } + v->flags &= ~(VAR_EXPORTED|VAR_REEXPORT); + /* + * If we are unexporting a list, + * remove each one from .MAKE.EXPORTED. + * If we are removing them all, + * just delete .MAKE.EXPORTED below. + */ + if (vlist == str) { + n = snprintf(tmp, sizeof(tmp), + "${" MAKE_EXPORTED ":N%s}", v->name); + if (n < (int)sizeof(tmp)) { + cp = Var_Subst(NULL, tmp, VAR_GLOBAL, VARF_WANTRES); + Var_Set(MAKE_EXPORTED, cp, VAR_GLOBAL, 0); + free(cp); + } + } + } + free(as); + free(av); + if (vlist != str) { + Var_Delete(MAKE_EXPORTED, VAR_GLOBAL); + free(vlist); + } + } +} + +/*- + *----------------------------------------------------------------------- + * Var_Set -- + * Set the variable name to the value val in the given context. + * + * Input: + * name name of variable to set + * val value to give to the variable + * ctxt context in which to set it + * + * Results: + * None. + * + * Side Effects: + * If the variable doesn't yet exist, a new record is created for it. + * Else the old value is freed and the new one stuck in its place + * + * Notes: + * The variable is searched for only in its context before being + * created in that context. I.e. if the context is VAR_GLOBAL, + * only VAR_GLOBAL->context is searched. Likewise if it is VAR_CMD, only + * VAR_CMD->context is searched. This is done to avoid the literally + * thousands of unnecessary strcmp's that used to be done to + * set, say, $(@) or $(<). + * If the context is VAR_GLOBAL though, we check if the variable + * was set in VAR_CMD from the command line and skip it if so. + *----------------------------------------------------------------------- + */ +void +Var_Set(const char *name, const char *val, GNode *ctxt, int flags) +{ + Var *v; + char *expanded_name = NULL; + + /* + * We only look for a variable in the given context since anything set + * here will override anything in a lower context, so there's not much + * point in searching them all just to save a bit of memory... + */ + if (strchr(name, '$') != NULL) { + expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES); + if (expanded_name[0] == 0) { + if (DEBUG(VAR)) { + fprintf(debug_file, "Var_Set(\"%s\", \"%s\", ...) " + "name expands to empty string - ignored\n", + name, val); + } + free(expanded_name); + return; + } + name = expanded_name; + } + if (ctxt == VAR_GLOBAL) { + v = VarFind(name, VAR_CMD, 0); + if (v != NULL) { + if ((v->flags & VAR_FROM_CMD)) { + if (DEBUG(VAR)) { + fprintf(debug_file, "%s:%s = %s ignored!\n", ctxt->name, name, val); + } + goto out; + } + VarFreeEnv(v, TRUE); + } + } + v = VarFind(name, ctxt, 0); + if (v == NULL) { + if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) { + /* + * This var would normally prevent the same name being added + * to VAR_GLOBAL, so delete it from there if needed. + * Otherwise -V name may show the wrong value. + */ + Var_Delete(name, VAR_GLOBAL); + } + VarAdd(name, val, ctxt); + } else { + Buf_Empty(&v->val); + if (val) + Buf_AddBytes(&v->val, strlen(val), val); + + if (DEBUG(VAR)) { + fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, val); + } + if ((v->flags & VAR_EXPORTED)) { + Var_Export1(name, VAR_EXPORT_PARENT); + } + } + /* + * Any variables given on the command line are automatically exported + * to the environment (as per POSIX standard) + */ + if (ctxt == VAR_CMD && (flags & VAR_NO_EXPORT) == 0) { + if (v == NULL) { + /* we just added it */ + v = VarFind(name, ctxt, 0); + } + if (v != NULL) + v->flags |= VAR_FROM_CMD; + /* + * If requested, don't export these in the environment + * individually. We still put them in MAKEOVERRIDES so + * that the command-line settings continue to override + * Makefile settings. + */ + if (varNoExportEnv != TRUE) + setenv(name, val ? val : "", 1); + + Var_Append(MAKEOVERRIDES, name, VAR_GLOBAL); + } + if (*name == '.') { + if (strcmp(name, SAVE_DOLLARS) == 0) + save_dollars = s2Boolean(val, save_dollars); + } + + out: + free(expanded_name); + if (v != NULL) + VarFreeEnv(v, TRUE); +} + +/*- + *----------------------------------------------------------------------- + * Var_Append -- + * The variable of the given name has the given value appended to it in + * the given context. + * + * Input: + * name name of variable to modify + * val String to append to it + * ctxt Context in which this should occur + * + * Results: + * None + * + * Side Effects: + * If the variable doesn't exist, it is created. Else the strings + * are concatenated (with a space in between). + * + * Notes: + * Only if the variable is being sought in the global context is the + * environment searched. + * XXX: Knows its calling circumstances in that if called with ctxt + * an actual target, it will only search that context since only + * a local variable could be being appended to. This is actually + * a big win and must be tolerated. + *----------------------------------------------------------------------- + */ +void +Var_Append(const char *name, const char *val, GNode *ctxt) +{ + Var *v; + Hash_Entry *h; + char *expanded_name = NULL; + + if (strchr(name, '$') != NULL) { + expanded_name = Var_Subst(NULL, name, ctxt, VARF_WANTRES); + if (expanded_name[0] == 0) { + if (DEBUG(VAR)) { + fprintf(debug_file, "Var_Append(\"%s\", \"%s\", ...) " + "name expands to empty string - ignored\n", + name, val); + } + free(expanded_name); + return; + } + name = expanded_name; + } + + v = VarFind(name, ctxt, (ctxt == VAR_GLOBAL) ? (FIND_CMD|FIND_ENV) : 0); + + if (v == NULL) { + Var_Set(name, val, ctxt, 0); + } else if (ctxt == VAR_CMD || !(v->flags & VAR_FROM_CMD)) { + Buf_AddByte(&v->val, ' '); + Buf_AddBytes(&v->val, strlen(val), val); + + if (DEBUG(VAR)) { + fprintf(debug_file, "%s:%s = %s\n", ctxt->name, name, + Buf_GetAll(&v->val, NULL)); + } + + if (v->flags & VAR_FROM_ENV) { + /* + * If the original variable came from the environment, we + * have to install it in the global context (we could place + * it in the environment, but then we should provide a way to + * export other variables...) + */ + v->flags &= ~VAR_FROM_ENV; + h = Hash_CreateEntry(&ctxt->context, name, NULL); + Hash_SetValue(h, v); + } + } + free(expanded_name); +} + +/*- + *----------------------------------------------------------------------- + * Var_Exists -- + * See if the given variable exists. + * + * Input: + * name Variable to find + * ctxt Context in which to start search + * + * Results: + * TRUE if it does, FALSE if it doesn't + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +Boolean +Var_Exists(const char *name, GNode *ctxt) +{ + Var *v; + char *cp; + + if ((cp = strchr(name, '$')) != NULL) { + cp = Var_Subst(NULL, name, ctxt, VARF_WANTRES); + } + v = VarFind(cp ? cp : name, ctxt, FIND_CMD|FIND_GLOBAL|FIND_ENV); + free(cp); + if (v == NULL) { + return(FALSE); + } else { + (void)VarFreeEnv(v, TRUE); + } + return(TRUE); +} + +/*- + *----------------------------------------------------------------------- + * Var_Value -- + * Return the value of the named variable in the given context + * + * Input: + * name name to find + * ctxt context in which to search for it + * + * Results: + * The value if the variable exists, NULL if it doesn't + * + * Side Effects: + * None + *----------------------------------------------------------------------- + */ +char * +Var_Value(const char *name, GNode *ctxt, char **frp) +{ + Var *v; + + v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + *frp = NULL; + if (v != NULL) { + char *p = (Buf_GetAll(&v->val, NULL)); + if (VarFreeEnv(v, FALSE)) + *frp = p; + return p; + } else { + return NULL; + } +} + +/*- + *----------------------------------------------------------------------- + * VarHead -- + * Remove the tail of the given word and place the result in the given + * buffer. + * + * Input: + * word Word to trim + * addSpace True if need to add a space to the buffer + * before sticking in the head + * buf Buffer in which to store it + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarHead(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *dummy MAKE_ATTR_UNUSED) +{ + char *slash; + + slash = strrchr(word, '/'); + if (slash != NULL) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + *slash = '\0'; + Buf_AddBytes(buf, strlen(word), word); + *slash = '/'; + return (TRUE); + } else { + /* + * If no directory part, give . (q.v. the POSIX standard) + */ + if (addSpace && vpstate->varSpace) + Buf_AddByte(buf, vpstate->varSpace); + Buf_AddByte(buf, '.'); + } + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * VarTail -- + * Remove the head of the given word and place the result in the given + * buffer. + * + * Input: + * word Word to trim + * addSpace True if need to add a space to the buffer + * before adding the tail + * buf Buffer in which to store it + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarTail(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *dummy MAKE_ATTR_UNUSED) +{ + char *slash; + + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + + slash = strrchr(word, '/'); + if (slash != NULL) { + *slash++ = '\0'; + Buf_AddBytes(buf, strlen(slash), slash); + slash[-1] = '/'; + } else { + Buf_AddBytes(buf, strlen(word), word); + } + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * VarSuffix -- + * Place the suffix of the given word in the given buffer. + * + * Input: + * word Word to trim + * addSpace TRUE if need to add a space before placing the + * suffix in the buffer + * buf Buffer in which to store it + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The suffix from the word is placed in the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSuffix(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *dummy MAKE_ATTR_UNUSED) +{ + char *dot; + + dot = strrchr(word, '.'); + if (dot != NULL) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + *dot++ = '\0'; + Buf_AddBytes(buf, strlen(dot), dot); + dot[-1] = '.'; + addSpace = TRUE; + } + return addSpace; +} + +/*- + *----------------------------------------------------------------------- + * VarRoot -- + * Remove the suffix of the given word and place the result in the + * buffer. + * + * Input: + * word Word to trim + * addSpace TRUE if need to add a space to the buffer + * before placing the root in it + * buf Buffer in which to store it + * + * Results: + * TRUE if characters were added to the buffer (a space needs to be + * added to the buffer before the next word). + * + * Side Effects: + * The trimmed word is added to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarRoot(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *dummy MAKE_ATTR_UNUSED) +{ + char *dot; + + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + + dot = strrchr(word, '.'); + if (dot != NULL) { + *dot = '\0'; + Buf_AddBytes(buf, strlen(word), word); + *dot = '.'; + } else { + Buf_AddBytes(buf, strlen(word), word); + } + return TRUE; +} + +/*- + *----------------------------------------------------------------------- + * VarMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the :M modifier. + * + * Input: + * word Word to examine + * addSpace TRUE if need to add a space to the buffer + * before adding the word, if it matches + * buf Buffer in which to store it + * pattern Pattern the word must match + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *pattern) +{ + if (DEBUG(VAR)) + fprintf(debug_file, "VarMatch [%s] [%s]\n", word, (char *)pattern); + if (Str_Match(word, (char *)pattern)) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), word); + } + return(addSpace); +} + +#ifdef SYSVVARSUB +/*- + *----------------------------------------------------------------------- + * VarSYSVMatch -- + * Place the word in the buffer if it matches the given pattern. + * Callback function for VarModify to implement the System V % + * modifiers. + * + * Input: + * word Word to examine + * addSpace TRUE if need to add a space to the buffer + * before adding the word, if it matches + * buf Buffer in which to store it + * patp Pattern the word must match + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSYSVMatch(GNode *ctx, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *patp) +{ + int len; + char *ptr; + VarPattern *pat = (VarPattern *)patp; + char *varexp; + + if (addSpace && vpstate->varSpace) + Buf_AddByte(buf, vpstate->varSpace); + + addSpace = TRUE; + + if ((ptr = Str_SYSVMatch(word, pat->lhs, &len)) != NULL) { + varexp = Var_Subst(NULL, pat->rhs, ctx, VARF_WANTRES); + Str_SYSVSubst(buf, varexp, ptr, len); + free(varexp); + } else { + Buf_AddBytes(buf, strlen(word), word); + } + + return(addSpace); +} +#endif + + +/*- + *----------------------------------------------------------------------- + * VarNoMatch -- + * Place the word in the buffer if it doesn't match the given pattern. + * Callback function for VarModify to implement the :N modifier. + * + * Input: + * word Word to examine + * addSpace TRUE if need to add a space to the buffer + * before adding the word, if it matches + * buf Buffer in which to store it + * pattern Pattern the word must match + * + * Results: + * TRUE if a space should be placed in the buffer before the next + * word. + * + * Side Effects: + * The word may be copied to the buffer. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarNoMatch(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *pattern) +{ + if (!Str_Match(word, (char *)pattern)) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + Buf_AddBytes(buf, strlen(word), word); + } + return(addSpace); +} + + +/*- + *----------------------------------------------------------------------- + * VarSubstitute -- + * Perform a string-substitution on the given word, placing the + * result in the passed buffer. + * + * Input: + * word Word to modify + * addSpace True if space should be added before + * other characters + * buf Buffer for result + * patternp Pattern for substitution + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarSubstitute(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *patternp) +{ + int wordLen; /* Length of word */ + char *cp; /* General pointer */ + VarPattern *pattern = (VarPattern *)patternp; + + wordLen = strlen(word); + if ((pattern->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) != + (VAR_SUB_ONE|VAR_SUB_MATCHED)) { + /* + * Still substituting -- break it down into simple anchored cases + * and if none of them fits, perform the general substitution case. + */ + if ((pattern->flags & VAR_MATCH_START) && + (strncmp(word, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Anchored at start and beginning of word matches pattern + */ + if ((pattern->flags & VAR_MATCH_END) && + (wordLen == pattern->leftLen)) { + /* + * Also anchored at end and matches to the end (word + * is same length as pattern) add space and rhs only + * if rhs is non-null. + */ + if (pattern->rightLen != 0) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + Buf_AddBytes(buf, pattern->rightLen, pattern->rhs); + } + pattern->flags |= VAR_SUB_MATCHED; + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Doesn't match to end -- copy word wholesale + */ + goto nosub; + } else { + /* + * Matches at start but need to copy in trailing characters + */ + if ((pattern->rightLen + wordLen - pattern->leftLen) != 0){ + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, pattern->rightLen, pattern->rhs); + Buf_AddBytes(buf, wordLen - pattern->leftLen, + (word + pattern->leftLen)); + pattern->flags |= VAR_SUB_MATCHED; + } + } else if (pattern->flags & VAR_MATCH_START) { + /* + * Had to match at start of word and didn't -- copy whole word. + */ + goto nosub; + } else if (pattern->flags & VAR_MATCH_END) { + /* + * Anchored at end, Find only place match could occur (leftLen + * characters from the end of the word) and see if it does. Note + * that because the $ will be left at the end of the lhs, we have + * to use strncmp. + */ + cp = word + (wordLen - pattern->leftLen); + if ((cp >= word) && + (strncmp(cp, pattern->lhs, pattern->leftLen) == 0)) { + /* + * Match found. If we will place characters in the buffer, + * add a space before hand as indicated by addSpace, then + * stuff in the initial, unmatched part of the word followed + * by the right-hand-side. + */ + if (((cp - word) + pattern->rightLen) != 0) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + } + Buf_AddBytes(buf, cp - word, word); + Buf_AddBytes(buf, pattern->rightLen, pattern->rhs); + pattern->flags |= VAR_SUB_MATCHED; + } else { + /* + * Had to match at end and didn't. Copy entire word. + */ + goto nosub; + } + } else { + /* + * Pattern is unanchored: search for the pattern in the word using + * String_FindSubstring, copying unmatched portions and the + * right-hand-side for each match found, handling non-global + * substitutions correctly, etc. When the loop is done, any + * remaining part of the word (word and wordLen are adjusted + * accordingly through the loop) is copied straight into the + * buffer. + * addSpace is set FALSE as soon as a space is added to the + * buffer. + */ + Boolean done; + int origSize; + + done = FALSE; + origSize = Buf_Size(buf); + while (!done) { + cp = Str_FindSubstring(word, pattern->lhs); + if (cp != NULL) { + if (addSpace && (((cp - word) + pattern->rightLen) != 0)){ + Buf_AddByte(buf, vpstate->varSpace); + addSpace = FALSE; + } + Buf_AddBytes(buf, cp-word, word); + Buf_AddBytes(buf, pattern->rightLen, pattern->rhs); + wordLen -= (cp - word) + pattern->leftLen; + word = cp + pattern->leftLen; + if (wordLen == 0) { + done = TRUE; + } + if ((pattern->flags & VAR_SUB_GLOBAL) == 0) { + done = TRUE; + } + pattern->flags |= VAR_SUB_MATCHED; + } else { + done = TRUE; + } + } + if (wordLen != 0) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + Buf_AddBytes(buf, wordLen, word); + } + /* + * If added characters to the buffer, need to add a space + * before we add any more. If we didn't add any, just return + * the previous value of addSpace. + */ + return ((Buf_Size(buf) != origSize) || addSpace); + } + return (addSpace); + } + nosub: + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + Buf_AddBytes(buf, wordLen, word); + return(TRUE); +} + +#ifndef NO_REGEX +/*- + *----------------------------------------------------------------------- + * VarREError -- + * Print the error caused by a regcomp or regexec call. + * + * Results: + * None. + * + * Side Effects: + * An error gets printed. + * + *----------------------------------------------------------------------- + */ +static void +VarREError(int reerr, regex_t *pat, const char *str) +{ + char *errbuf; + int errlen; + + errlen = regerror(reerr, pat, 0, 0); + errbuf = bmake_malloc(errlen); + regerror(reerr, pat, errbuf, errlen); + Error("%s: %s", str, errbuf); + free(errbuf); +} + + +/*- + *----------------------------------------------------------------------- + * VarRESubstitute -- + * Perform a regex substitution on the given word, placing the + * result in the passed buffer. + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarRESubstitute(GNode *ctx MAKE_ATTR_UNUSED, + Var_Parse_State *vpstate MAKE_ATTR_UNUSED, + char *word, Boolean addSpace, Buffer *buf, + void *patternp) +{ + VarREPattern *pat; + int xrv; + char *wp; + char *rp; + int added; + int flags = 0; + +#define MAYBE_ADD_SPACE() \ + if (addSpace && !added) \ + Buf_AddByte(buf, ' '); \ + added = 1 + + added = 0; + wp = word; + pat = patternp; + + if ((pat->flags & (VAR_SUB_ONE|VAR_SUB_MATCHED)) == + (VAR_SUB_ONE|VAR_SUB_MATCHED)) + xrv = REG_NOMATCH; + else { + tryagain: + xrv = regexec(&pat->re, wp, pat->nsub, pat->matches, flags); + } + + switch (xrv) { + case 0: + pat->flags |= VAR_SUB_MATCHED; + if (pat->matches[0].rm_so > 0) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, pat->matches[0].rm_so, wp); + } + + for (rp = pat->replace; *rp; rp++) { + if ((*rp == '\\') && ((rp[1] == '&') || (rp[1] == '\\'))) { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf,rp[1]); + rp++; + } + else if ((*rp == '&') || + ((*rp == '\\') && isdigit((unsigned char)rp[1]))) { + int n; + const char *subbuf; + int sublen; + char errstr[3]; + + if (*rp == '&') { + n = 0; + errstr[0] = '&'; + errstr[1] = '\0'; + } else { + n = rp[1] - '0'; + errstr[0] = '\\'; + errstr[1] = rp[1]; + errstr[2] = '\0'; + rp++; + } + + if (n > pat->nsub) { + Error("No subexpression %s", &errstr[0]); + subbuf = ""; + sublen = 0; + } else if ((pat->matches[n].rm_so == -1) && + (pat->matches[n].rm_eo == -1)) { + Error("No match for subexpression %s", &errstr[0]); + subbuf = ""; + sublen = 0; + } else { + subbuf = wp + pat->matches[n].rm_so; + sublen = pat->matches[n].rm_eo - pat->matches[n].rm_so; + } + + if (sublen > 0) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, sublen, subbuf); + } + } else { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf, *rp); + } + } + wp += pat->matches[0].rm_eo; + if (pat->flags & VAR_SUB_GLOBAL) { + flags |= REG_NOTBOL; + if (pat->matches[0].rm_so == 0 && pat->matches[0].rm_eo == 0) { + MAYBE_ADD_SPACE(); + Buf_AddByte(buf, *wp); + wp++; + + } + if (*wp) + goto tryagain; + } + if (*wp) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf, strlen(wp), wp); + } + break; + default: + VarREError(xrv, &pat->re, "Unexpected regex error"); + /* fall through */ + case REG_NOMATCH: + if (*wp) { + MAYBE_ADD_SPACE(); + Buf_AddBytes(buf,strlen(wp),wp); + } + break; + } + return(addSpace||added); +} +#endif + + + +/*- + *----------------------------------------------------------------------- + * VarLoopExpand -- + * Implements the :@@@ modifier of ODE make. + * We set the temp variable named in pattern.lhs to word and expand + * pattern.rhs storing the result in the passed buffer. + * + * Input: + * word Word to modify + * addSpace True if space should be added before + * other characters + * buf Buffer for result + * pattern Datafor substitution + * + * Results: + * TRUE if a space is needed before more characters are added. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static Boolean +VarLoopExpand(GNode *ctx MAKE_ATTR_UNUSED, + Var_Parse_State *vpstate MAKE_ATTR_UNUSED, + char *word, Boolean addSpace, Buffer *buf, + void *loopp) +{ + VarLoop_t *loop = (VarLoop_t *)loopp; + char *s; + int slen; + + if (word && *word) { + Var_Set(loop->tvar, word, loop->ctxt, VAR_NO_EXPORT); + s = Var_Subst(NULL, loop->str, loop->ctxt, loop->errnum | VARF_WANTRES); + if (s != NULL && *s != '\0') { + if (addSpace && *s != '\n') + Buf_AddByte(buf, ' '); + Buf_AddBytes(buf, (slen = strlen(s)), s); + addSpace = (slen > 0 && s[slen - 1] != '\n'); + } + free(s); + } + return addSpace; +} + + +/*- + *----------------------------------------------------------------------- + * VarSelectWords -- + * Implements the :[start..end] modifier. + * This is a special case of VarModify since we want to be able + * to scan the list backwards if start > end. + * + * Input: + * str String whose words should be trimmed + * seldata words to select + * + * Results: + * A string of all the words selected. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarSelectWords(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + const char *str, VarSelectWords_t *seldata) +{ + Buffer buf; /* Buffer for the new string */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the trimmed + * word */ + char **av; /* word list */ + char *as; /* word list memory */ + int ac, i; + int start, end, step; + + Buf_Init(&buf, 0); + addSpace = FALSE; + + if (vpstate->oneBigWord) { + /* fake what brk_string() would do if there were only one word */ + ac = 1; + av = bmake_malloc((ac + 1) * sizeof(char *)); + as = bmake_strdup(str); + av[0] = as; + av[1] = NULL; + } else { + av = brk_string(str, &ac, FALSE, &as); + } + + /* + * Now sanitize seldata. + * If seldata->start or seldata->end are negative, convert them to + * the positive equivalents (-1 gets converted to argc, -2 gets + * converted to (argc-1), etc.). + */ + if (seldata->start < 0) + seldata->start = ac + seldata->start + 1; + if (seldata->end < 0) + seldata->end = ac + seldata->end + 1; + + /* + * We avoid scanning more of the list than we need to. + */ + if (seldata->start > seldata->end) { + start = MIN(ac, seldata->start) - 1; + end = MAX(0, seldata->end - 1); + step = -1; + } else { + start = MAX(0, seldata->start - 1); + end = MIN(ac, seldata->end); + step = 1; + } + + for (i = start; + (step < 0 && i >= end) || (step > 0 && i < end); + i += step) { + if (av[i] && *av[i]) { + if (addSpace && vpstate->varSpace) { + Buf_AddByte(&buf, vpstate->varSpace); + } + Buf_AddBytes(&buf, strlen(av[i]), av[i]); + addSpace = TRUE; + } + } + + free(as); + free(av); + + return Buf_Destroy(&buf, FALSE); +} + + +/*- + * VarRealpath -- + * Replace each word with the result of realpath() + * if successful. + */ +static Boolean +VarRealpath(GNode *ctx MAKE_ATTR_UNUSED, Var_Parse_State *vpstate, + char *word, Boolean addSpace, Buffer *buf, + void *patternp MAKE_ATTR_UNUSED) +{ + struct stat st; + char rbuf[MAXPATHLEN]; + char *rp; + + if (addSpace && vpstate->varSpace) { + Buf_AddByte(buf, vpstate->varSpace); + } + addSpace = TRUE; + rp = cached_realpath(word, rbuf); + if (rp && *rp == '/' && stat(rp, &st) == 0) + word = rp; + + Buf_AddBytes(buf, strlen(word), word); + return(addSpace); +} + +/*- + *----------------------------------------------------------------------- + * VarModify -- + * Modify each of the words of the passed string using the given + * function. Used to implement all modifiers. + * + * Input: + * str String whose words should be trimmed + * modProc Function to use to modify them + * datum Datum to pass it + * + * Results: + * A string of all the words modified appropriately. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarModify(GNode *ctx, Var_Parse_State *vpstate, + const char *str, + Boolean (*modProc)(GNode *, Var_Parse_State *, char *, + Boolean, Buffer *, void *), + void *datum) +{ + Buffer buf; /* Buffer for the new string */ + Boolean addSpace; /* TRUE if need to add a space to the + * buffer before adding the trimmed + * word */ + char **av; /* word list */ + char *as; /* word list memory */ + int ac, i; + + Buf_Init(&buf, 0); + addSpace = FALSE; + + if (vpstate->oneBigWord) { + /* fake what brk_string() would do if there were only one word */ + ac = 1; + av = bmake_malloc((ac + 1) * sizeof(char *)); + as = bmake_strdup(str); + av[0] = as; + av[1] = NULL; + } else { + av = brk_string(str, &ac, FALSE, &as); + } + + for (i = 0; i < ac; i++) { + addSpace = (*modProc)(ctx, vpstate, av[i], addSpace, &buf, datum); + } + + free(as); + free(av); + + return Buf_Destroy(&buf, FALSE); +} + + +static int +VarWordCompare(const void *a, const void *b) +{ + int r = strcmp(*(const char * const *)a, *(const char * const *)b); + return r; +} + +/*- + *----------------------------------------------------------------------- + * VarOrder -- + * Order the words in the string. + * + * Input: + * str String whose words should be sorted. + * otype How to order: s - sort, x - random. + * + * Results: + * A string containing the words ordered. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarOrder(const char *str, const char otype) +{ + Buffer buf; /* Buffer for the new string */ + char **av; /* word list [first word does not count] */ + char *as; /* word list memory */ + int ac, i; + + Buf_Init(&buf, 0); + + av = brk_string(str, &ac, FALSE, &as); + + if (ac > 0) + switch (otype) { + case 's': /* sort alphabetically */ + qsort(av, ac, sizeof(char *), VarWordCompare); + break; + case 'x': /* randomize */ + { + int rndidx; + char *t; + + /* + * We will use [ac..2] range for mod factors. This will produce + * random numbers in [(ac-1)..0] interval, and minimal + * reasonable value for mod factor is 2 (the mod 1 will produce + * 0 with probability 1). + */ + for (i = ac-1; i > 0; i--) { + rndidx = random() % (i + 1); + if (i != rndidx) { + t = av[i]; + av[i] = av[rndidx]; + av[rndidx] = t; + } + } + } + } /* end of switch */ + + for (i = 0; i < ac; i++) { + Buf_AddBytes(&buf, strlen(av[i]), av[i]); + if (i != ac - 1) + Buf_AddByte(&buf, ' '); + } + + free(as); + free(av); + + return Buf_Destroy(&buf, FALSE); +} + + +/*- + *----------------------------------------------------------------------- + * VarUniq -- + * Remove adjacent duplicate words. + * + * Input: + * str String whose words should be sorted + * + * Results: + * A string containing the resulting words. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarUniq(const char *str) +{ + Buffer buf; /* Buffer for new string */ + char **av; /* List of words to affect */ + char *as; /* Word list memory */ + int ac, i, j; + + Buf_Init(&buf, 0); + av = brk_string(str, &ac, FALSE, &as); + + if (ac > 1) { + for (j = 0, i = 1; i < ac; i++) + if (strcmp(av[i], av[j]) != 0 && (++j != i)) + av[j] = av[i]; + ac = j + 1; + } + + for (i = 0; i < ac; i++) { + Buf_AddBytes(&buf, strlen(av[i]), av[i]); + if (i != ac - 1) + Buf_AddByte(&buf, ' '); + } + + free(as); + free(av); + + return Buf_Destroy(&buf, FALSE); +} + +/*- + *----------------------------------------------------------------------- + * VarRange -- + * Return an integer sequence + * + * Input: + * str String whose words provide default range + * ac range length, if 0 use str words + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarRange(const char *str, int ac) +{ + Buffer buf; /* Buffer for new string */ + char tmp[32]; /* each element */ + char **av; /* List of words to affect */ + char *as; /* Word list memory */ + int i, n; + + Buf_Init(&buf, 0); + if (ac > 0) { + as = NULL; + av = NULL; + } else { + av = brk_string(str, &ac, FALSE, &as); + } + for (i = 0; i < ac; i++) { + n = snprintf(tmp, sizeof(tmp), "%d", 1 + i); + if (n >= (int)sizeof(tmp)) + break; + Buf_AddBytes(&buf, n, tmp); + if (i != ac - 1) + Buf_AddByte(&buf, ' '); + } + + free(as); + free(av); + + return Buf_Destroy(&buf, FALSE); +} + + +/*- + *----------------------------------------------------------------------- + * VarGetPattern -- + * Pass through the tstr looking for 1) escaped delimiters, + * '$'s and backslashes (place the escaped character in + * uninterpreted) and 2) unescaped $'s that aren't before + * the delimiter (expand the variable substitution unless flags + * has VAR_NOSUBST set). + * Return the expanded string or NULL if the delimiter was missing + * If pattern is specified, handle escaped ampersands, and replace + * unescaped ampersands with the lhs of the pattern. + * + * Results: + * A string of all the words modified appropriately. + * If length is specified, return the string length of the buffer + * If flags is specified and the last character of the pattern is a + * $ set the VAR_MATCH_END bit of flags. + * + * Side Effects: + * None. + *----------------------------------------------------------------------- + */ +static char * +VarGetPattern(GNode *ctxt, Var_Parse_State *vpstate MAKE_ATTR_UNUSED, + int flags, const char **tstr, int delim, int *vflags, + int *length, VarPattern *pattern) +{ + const char *cp; + char *rstr; + Buffer buf; + int junk; + int errnum = flags & VARF_UNDEFERR; + + Buf_Init(&buf, 0); + if (length == NULL) + length = &junk; + +#define IS_A_MATCH(cp, delim) \ + ((cp[0] == '\\') && ((cp[1] == delim) || \ + (cp[1] == '\\') || (cp[1] == '$') || (pattern && (cp[1] == '&')))) + + /* + * Skim through until the matching delimiter is found; + * pick up variable substitutions on the way. Also allow + * backslashes to quote the delimiter, $, and \, but don't + * touch other backslashes. + */ + for (cp = *tstr; *cp && (*cp != delim); cp++) { + if (IS_A_MATCH(cp, delim)) { + Buf_AddByte(&buf, cp[1]); + cp++; + } else if (*cp == '$') { + if (cp[1] == delim) { + if (vflags == NULL) + Buf_AddByte(&buf, *cp); + else + /* + * Unescaped $ at end of pattern => anchor + * pattern at end. + */ + *vflags |= VAR_MATCH_END; + } else { + if (vflags == NULL || (*vflags & VAR_NOSUBST) == 0) { + char *cp2; + int len; + void *freeIt; + + /* + * If unescaped dollar sign not before the + * delimiter, assume it's a variable + * substitution and recurse. + */ + cp2 = Var_Parse(cp, ctxt, errnum | VARF_WANTRES, &len, + &freeIt); + Buf_AddBytes(&buf, strlen(cp2), cp2); + free(freeIt); + cp += len - 1; + } else { + const char *cp2 = &cp[1]; + + if (*cp2 == PROPEN || *cp2 == BROPEN) { + /* + * Find the end of this variable reference + * and suck it in without further ado. + * It will be interperated later. + */ + int have = *cp2; + int want = (*cp2 == PROPEN) ? PRCLOSE : BRCLOSE; + int depth = 1; + + for (++cp2; *cp2 != '\0' && depth > 0; ++cp2) { + if (cp2[-1] != '\\') { + if (*cp2 == have) + ++depth; + if (*cp2 == want) + --depth; + } + } + Buf_AddBytes(&buf, cp2 - cp, cp); + cp = --cp2; + } else + Buf_AddByte(&buf, *cp); + } + } + } + else if (pattern && *cp == '&') + Buf_AddBytes(&buf, pattern->leftLen, pattern->lhs); + else + Buf_AddByte(&buf, *cp); + } + + if (*cp != delim) { + *tstr = cp; + *length = 0; + return NULL; + } + + *tstr = ++cp; + *length = Buf_Size(&buf); + rstr = Buf_Destroy(&buf, FALSE); + if (DEBUG(VAR)) + fprintf(debug_file, "Modifier pattern: \"%s\"\n", rstr); + return rstr; +} + +/*- + *----------------------------------------------------------------------- + * VarQuote -- + * Quote shell meta-characters and space characters in the string + * if quoteDollar is set, also quote and double any '$' characters. + * + * Results: + * The quoted string + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarQuote(char *str, Boolean quoteDollar) +{ + + Buffer buf; + const char *newline; + size_t nlen; + + if ((newline = Shell_GetNewline()) == NULL) + newline = "\\\n"; + nlen = strlen(newline); + + Buf_Init(&buf, 0); + + for (; *str != '\0'; str++) { + if (*str == '\n') { + Buf_AddBytes(&buf, nlen, newline); + continue; + } + if (isspace((unsigned char)*str) || ismeta((unsigned char)*str)) + Buf_AddByte(&buf, '\\'); + Buf_AddByte(&buf, *str); + if (quoteDollar && *str == '$') + Buf_AddBytes(&buf, 2, "\\$"); + } + + str = Buf_Destroy(&buf, FALSE); + if (DEBUG(VAR)) + fprintf(debug_file, "QuoteMeta: [%s]\n", str); + return str; +} + +/*- + *----------------------------------------------------------------------- + * VarHash -- + * Hash the string using the MurmurHash3 algorithm. + * Output is computed using 32bit Little Endian arithmetic. + * + * Input: + * str String to modify + * + * Results: + * Hash value of str, encoded as 8 hex digits. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +static char * +VarHash(char *str) +{ + static const char hexdigits[16] = "0123456789abcdef"; + Buffer buf; + size_t len, len2; + unsigned char *ustr = (unsigned char *)str; + uint32_t h, k, c1, c2; + + h = 0x971e137bU; + c1 = 0x95543787U; + c2 = 0x2ad7eb25U; + len2 = strlen(str); + + for (len = len2; len; ) { + k = 0; + switch (len) { + default: + k = (ustr[3] << 24) | (ustr[2] << 16) | (ustr[1] << 8) | ustr[0]; + len -= 4; + ustr += 4; + break; + case 3: + k |= (ustr[2] << 16); + case 2: + k |= (ustr[1] << 8); + case 1: + k |= ustr[0]; + len = 0; + } + c1 = c1 * 5 + 0x7b7d159cU; + c2 = c2 * 5 + 0x6bce6396U; + k *= c1; + k = (k << 11) ^ (k >> 21); + k *= c2; + h = (h << 13) ^ (h >> 19); + h = h * 5 + 0x52dce729U; + h ^= k; + } + h ^= len2; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + Buf_Init(&buf, 0); + for (len = 0; len < 8; ++len) { + Buf_AddByte(&buf, hexdigits[h & 15]); + h >>= 4; + } + + return Buf_Destroy(&buf, FALSE); +} + +static char * +VarStrftime(const char *fmt, int zulu, time_t utc) +{ + char buf[BUFSIZ]; + + if (!utc) + time(&utc); + if (!*fmt) + fmt = "%c"; + strftime(buf, sizeof(buf), fmt, zulu ? gmtime(&utc) : localtime(&utc)); + + buf[sizeof(buf) - 1] = '\0'; + return bmake_strdup(buf); +} + +/* + * Now we need to apply any modifiers the user wants applied. + * These are: + * :M words which match the given . + * is of the standard file + * wildcarding form. + * :N words which do not match the given . + * :S[1gW] + * Substitute for in the value + * :C[1gW] + * Substitute for regex in the value + * :H Substitute the head of each word + * :T Substitute the tail of each word + * :E Substitute the extension (minus '.') of + * each word + * :R Substitute the root of each word + * (pathname minus the suffix). + * :O ("Order") Alphabeticaly sort words in variable. + * :Ox ("intermiX") Randomize words in variable. + * :u ("uniq") Remove adjacent duplicate words. + * :tu Converts the variable contents to uppercase. + * :tl Converts the variable contents to lowercase. + * :ts[c] Sets varSpace - the char used to + * separate words to 'c'. If 'c' is + * omitted then no separation is used. + * :tW Treat the variable contents as a single + * word, even if it contains spaces. + * (Mnemonic: one big 'W'ord.) + * :tw Treat the variable contents as multiple + * space-separated words. + * (Mnemonic: many small 'w'ords.) + * :[index] Select a single word from the value. + * :[start..end] Select multiple words from the value. + * :[*] or :[0] Select the entire value, as a single + * word. Equivalent to :tW. + * :[@] Select the entire value, as multiple + * words. Undoes the effect of :[*]. + * Equivalent to :tw. + * :[#] Returns the number of words in the value. + * + * :?: + * If the variable evaluates to true, return + * true value, else return the second value. + * :lhs=rhs Like :S, but the rhs goes to the end of + * the invocation. + * :sh Treat the current value as a command + * to be run, new value is its output. + * The following added so we can handle ODE makefiles. + * :@@@ + * Assign a temporary local variable + * to the current value of each word in turn + * and replace each word with the result of + * evaluating + * :D Use as value if variable defined + * :U Use as value if variable undefined + * :L Use the name of the variable as the value. + * :P Use the path of the node that has the same + * name as the variable as the value. This + * basically includes an implied :L so that + * the common method of refering to the path + * of your dependent 'x' in a rule is to use + * the form '${x:P}'. + * :!! Run cmd much the same as :sh run's the + * current value of the variable. + * The ::= modifiers, actually assign a value to the variable. + * Their main purpose is in supporting modifiers of .for loop + * iterators and other obscure uses. They always expand to + * nothing. In a target rule that would otherwise expand to an + * empty line they can be preceded with @: to keep make happy. + * Eg. + * + * foo: .USE + * .for i in ${.TARGET} ${.TARGET:R}.gz + * @: ${t::=$i} + * @echo blah ${t:T} + * .endfor + * + * ::= Assigns as the new value of variable. + * ::?= Assigns as value of variable if + * it was not already set. + * ::+= Appends to variable. + * ::!= Assigns output of as the new value of + * variable. + */ + +/* we now have some modifiers with long names */ +#define STRMOD_MATCH(s, want, n) \ + (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':')) +#define STRMOD_MATCHX(s, want, n) \ + (strncmp(s, want, n) == 0 && (s[n] == endc || s[n] == ':' || s[n] == '=')) +#define CHARMOD_MATCH(c) (c == endc || c == ':') + +static char * +ApplyModifiers(char *nstr, const char *tstr, + int startc, int endc, + Var *v, GNode *ctxt, int flags, + int *lengthPtr, void **freePtr) +{ + const char *start; + const char *cp; /* Secondary pointer into str (place marker + * for tstr) */ + char *newStr; /* New value to return */ + char *ep; + char termc; /* Character which terminated scan */ + int cnt; /* Used to count brace pairs when variable in + * in parens or braces */ + char delim; + int modifier; /* that we are processing */ + Var_Parse_State parsestate; /* Flags passed to helper functions */ + time_t utc; /* for VarStrftime */ + + delim = '\0'; + parsestate.oneBigWord = FALSE; + parsestate.varSpace = ' '; /* word separator */ + + start = cp = tstr; + + while (*tstr && *tstr != endc) { + + if (*tstr == '$') { + /* + * We may have some complex modifiers in a variable. + */ + void *freeIt; + char *rval; + int rlen; + int c; + + rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt); + + /* + * If we have not parsed up to endc or ':', + * we are not interested. + */ + if (rval != NULL && *rval && + (c = tstr[rlen]) != '\0' && + c != ':' && + c != endc) { + free(freeIt); + goto apply_mods; + } + + if (DEBUG(VAR)) { + fprintf(debug_file, "Got '%s' from '%.*s'%.*s\n", + rval, rlen, tstr, rlen, tstr + rlen); + } + + tstr += rlen; + + if (rval != NULL && *rval) { + int used; + + nstr = ApplyModifiers(nstr, rval, + 0, 0, v, ctxt, flags, &used, freePtr); + if (nstr == var_Error + || (nstr == varNoError && (flags & VARF_UNDEFERR) == 0) + || strlen(rval) != (size_t) used) { + free(freeIt); + goto out; /* error already reported */ + } + } + free(freeIt); + if (*tstr == ':') + tstr++; + else if (!*tstr && endc) { + Error("Unclosed variable specification after complex modifier (expecting '%c') for %s", endc, v->name); + goto out; + } + continue; + } + apply_mods: + if (DEBUG(VAR)) { + fprintf(debug_file, "Applying[%s] :%c to \"%s\"\n", v->name, + *tstr, nstr); + } + newStr = var_Error; + switch ((modifier = *tstr)) { + case ':': + { + if (tstr[1] == '=' || + (tstr[2] == '=' && + (tstr[1] == '!' || tstr[1] == '+' || tstr[1] == '?'))) { + /* + * "::=", "::!=", "::+=", or "::?=" + */ + GNode *v_ctxt; /* context where v belongs */ + const char *emsg; + char *sv_name; + VarPattern pattern; + int how; + int vflags; + + if (v->name[0] == 0) + goto bad_modifier; + + v_ctxt = ctxt; + sv_name = NULL; + ++tstr; + if (v->flags & VAR_JUNK) { + /* + * We need to bmake_strdup() it incase + * VarGetPattern() recurses. + */ + sv_name = v->name; + v->name = bmake_strdup(v->name); + } else if (ctxt != VAR_GLOBAL) { + Var *gv = VarFind(v->name, ctxt, 0); + if (gv == NULL) + v_ctxt = VAR_GLOBAL; + else + VarFreeEnv(gv, TRUE); + } + + switch ((how = *tstr)) { + case '+': + case '?': + case '!': + cp = &tstr[2]; + break; + default: + cp = ++tstr; + break; + } + delim = startc == PROPEN ? PRCLOSE : BRCLOSE; + pattern.flags = 0; + + vflags = (flags & VARF_WANTRES) ? 0 : VAR_NOSUBST; + pattern.rhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, &vflags, + &pattern.rightLen, + NULL); + if (v->flags & VAR_JUNK) { + /* restore original name */ + free(v->name); + v->name = sv_name; + } + if (pattern.rhs == NULL) + goto cleanup; + + termc = *--cp; + delim = '\0'; + + if (flags & VARF_WANTRES) { + switch (how) { + case '+': + Var_Append(v->name, pattern.rhs, v_ctxt); + break; + case '!': + newStr = Cmd_Exec(pattern.rhs, &emsg); + if (emsg) + Error(emsg, nstr); + else + Var_Set(v->name, newStr, v_ctxt, 0); + free(newStr); + break; + case '?': + if ((v->flags & VAR_JUNK) == 0) + break; + /* FALLTHROUGH */ + default: + Var_Set(v->name, pattern.rhs, v_ctxt, 0); + break; + } + } + free(UNCONST(pattern.rhs)); + newStr = varNoError; + break; + } + goto default_case; /* "::" */ + } + case '@': + { + VarLoop_t loop; + int vflags = VAR_NOSUBST; + + cp = ++tstr; + delim = '@'; + if ((loop.tvar = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, + &vflags, &loop.tvarLen, + NULL)) == NULL) + goto cleanup; + + if ((loop.str = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, + &vflags, &loop.strLen, + NULL)) == NULL) + goto cleanup; + + termc = *cp; + delim = '\0'; + + loop.errnum = flags & VARF_UNDEFERR; + loop.ctxt = ctxt; + newStr = VarModify(ctxt, &parsestate, nstr, VarLoopExpand, + &loop); + Var_Delete(loop.tvar, ctxt); + free(loop.tvar); + free(loop.str); + break; + } + case '_': /* remember current value */ + cp = tstr + 1; /* make sure it is set */ + if (STRMOD_MATCHX(tstr, "_", 1)) { + if (tstr[1] == '=') { + char *np; + int n; + + cp++; + n = strcspn(cp, ":)}"); + np = bmake_strndup(cp, n+1); + np[n] = '\0'; + cp = tstr + 2 + n; + Var_Set(np, nstr, ctxt, 0); + free(np); + } else { + Var_Set("_", nstr, ctxt, 0); + } + newStr = nstr; + termc = *cp; + break; + } + goto default_case; + case 'D': + case 'U': + { + Buffer buf; /* Buffer for patterns */ + int nflags; + + if (flags & VARF_WANTRES) { + int wantres; + if (*tstr == 'U') + wantres = ((v->flags & VAR_JUNK) != 0); + else + wantres = ((v->flags & VAR_JUNK) == 0); + nflags = flags & ~VARF_WANTRES; + if (wantres) + nflags |= VARF_WANTRES; + } else + nflags = flags; + /* + * Pass through tstr looking for 1) escaped delimiters, + * '$'s and backslashes (place the escaped character in + * uninterpreted) and 2) unescaped $'s that aren't before + * the delimiter (expand the variable substitution). + * The result is left in the Buffer buf. + */ + Buf_Init(&buf, 0); + for (cp = tstr + 1; + *cp != endc && *cp != ':' && *cp != '\0'; + cp++) { + if ((*cp == '\\') && + ((cp[1] == ':') || + (cp[1] == '$') || + (cp[1] == endc) || + (cp[1] == '\\'))) + { + Buf_AddByte(&buf, cp[1]); + cp++; + } else if (*cp == '$') { + /* + * If unescaped dollar sign, assume it's a + * variable substitution and recurse. + */ + char *cp2; + int len; + void *freeIt; + + cp2 = Var_Parse(cp, ctxt, nflags, &len, &freeIt); + Buf_AddBytes(&buf, strlen(cp2), cp2); + free(freeIt); + cp += len - 1; + } else { + Buf_AddByte(&buf, *cp); + } + } + + termc = *cp; + + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + if (nflags & VARF_WANTRES) { + newStr = Buf_Destroy(&buf, FALSE); + } else { + newStr = nstr; + Buf_Destroy(&buf, TRUE); + } + break; + } + case 'L': + { + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + newStr = bmake_strdup(v->name); + cp = ++tstr; + termc = *tstr; + break; + } + case 'P': + { + GNode *gn; + + if ((v->flags & VAR_JUNK) != 0) + v->flags |= VAR_KEEP; + gn = Targ_FindNode(v->name, TARG_NOCREATE); + if (gn == NULL || gn->type & OP_NOPATH) { + newStr = NULL; + } else if (gn->path) { + newStr = bmake_strdup(gn->path); + } else { + newStr = Dir_FindFile(v->name, Suff_FindPath(gn)); + } + if (!newStr) { + newStr = bmake_strdup(v->name); + } + cp = ++tstr; + termc = *tstr; + break; + } + case '!': + { + const char *emsg; + VarPattern pattern; + pattern.flags = 0; + + delim = '!'; + emsg = NULL; + cp = ++tstr; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, + NULL, &pattern.rightLen, + NULL)) == NULL) + goto cleanup; + if (flags & VARF_WANTRES) + newStr = Cmd_Exec(pattern.rhs, &emsg); + else + newStr = varNoError; + free(UNCONST(pattern.rhs)); + if (emsg) + Error(emsg, nstr); + termc = *cp; + delim = '\0'; + if (v->flags & VAR_JUNK) { + v->flags |= VAR_KEEP; + } + break; + } + case '[': + { + /* + * Look for the closing ']', recursively + * expanding any embedded variables. + * + * estr is a pointer to the expanded result, + * which we must free(). + */ + char *estr; + + cp = tstr+1; /* point to char after '[' */ + delim = ']'; /* look for closing ']' */ + estr = VarGetPattern(ctxt, &parsestate, + flags, &cp, delim, + NULL, NULL, NULL); + if (estr == NULL) + goto cleanup; /* report missing ']' */ + /* now cp points just after the closing ']' */ + delim = '\0'; + if (cp[0] != ':' && cp[0] != endc) { + /* Found junk after ']' */ + free(estr); + goto bad_modifier; + } + if (estr[0] == '\0') { + /* Found empty square brackets in ":[]". */ + free(estr); + goto bad_modifier; + } else if (estr[0] == '#' && estr[1] == '\0') { + /* Found ":[#]" */ + + /* + * We will need enough space for the decimal + * representation of an int. We calculate the + * space needed for the octal representation, + * and add enough slop to cope with a '-' sign + * (which should never be needed) and a '\0' + * string terminator. + */ + int newStrSize = + (sizeof(int) * CHAR_BIT + 2) / 3 + 2; + + newStr = bmake_malloc(newStrSize); + if (parsestate.oneBigWord) { + strncpy(newStr, "1", newStrSize); + } else { + /* XXX: brk_string() is a rather expensive + * way of counting words. */ + char **av; + char *as; + int ac; + + av = brk_string(nstr, &ac, FALSE, &as); + snprintf(newStr, newStrSize, "%d", ac); + free(as); + free(av); + } + termc = *cp; + free(estr); + break; + } else if (estr[0] == '*' && estr[1] == '\0') { + /* Found ":[*]" */ + parsestate.oneBigWord = TRUE; + newStr = nstr; + termc = *cp; + free(estr); + break; + } else if (estr[0] == '@' && estr[1] == '\0') { + /* Found ":[@]" */ + parsestate.oneBigWord = FALSE; + newStr = nstr; + termc = *cp; + free(estr); + break; + } else { + /* + * We expect estr to contain a single + * integer for :[N], or two integers + * separated by ".." for :[start..end]. + */ + VarSelectWords_t seldata = { 0, 0 }; + + seldata.start = strtol(estr, &ep, 0); + if (ep == estr) { + /* Found junk instead of a number */ + free(estr); + goto bad_modifier; + } else if (ep[0] == '\0') { + /* Found only one integer in :[N] */ + seldata.end = seldata.start; + } else if (ep[0] == '.' && ep[1] == '.' && + ep[2] != '\0') { + /* Expecting another integer after ".." */ + ep += 2; + seldata.end = strtol(ep, &ep, 0); + if (ep[0] != '\0') { + /* Found junk after ".." */ + free(estr); + goto bad_modifier; + } + } else { + /* Found junk instead of ".." */ + free(estr); + goto bad_modifier; + } + /* + * Now seldata is properly filled in, + * but we still have to check for 0 as + * a special case. + */ + if (seldata.start == 0 && seldata.end == 0) { + /* ":[0]" or perhaps ":[0..0]" */ + parsestate.oneBigWord = TRUE; + newStr = nstr; + termc = *cp; + free(estr); + break; + } else if (seldata.start == 0 || + seldata.end == 0) { + /* ":[0..N]" or ":[N..0]" */ + free(estr); + goto bad_modifier; + } + /* + * Normal case: select the words + * described by seldata. + */ + newStr = VarSelectWords(ctxt, &parsestate, + nstr, &seldata); + + termc = *cp; + free(estr); + break; + } + + } + case 'g': + cp = tstr + 1; /* make sure it is set */ + if (STRMOD_MATCHX(tstr, "gmtime", 6)) { + if (tstr[6] == '=') { + utc = strtoul(&tstr[7], &ep, 10); + cp = ep; + } else { + utc = 0; + cp = tstr + 6; + } + newStr = VarStrftime(nstr, 1, utc); + termc = *cp; + } else { + goto default_case; + } + break; + case 'h': + cp = tstr + 1; /* make sure it is set */ + if (STRMOD_MATCH(tstr, "hash", 4)) { + newStr = VarHash(nstr); + cp = tstr + 4; + termc = *cp; + } else { + goto default_case; + } + break; + case 'l': + cp = tstr + 1; /* make sure it is set */ + if (STRMOD_MATCHX(tstr, "localtime", 9)) { + if (tstr[9] == '=') { + utc = strtoul(&tstr[10], &ep, 10); + cp = ep; + } else { + utc = 0; + cp = tstr + 9; + } + newStr = VarStrftime(nstr, 0, utc); + termc = *cp; + } else { + goto default_case; + } + break; + case 't': + { + cp = tstr + 1; /* make sure it is set */ + if (tstr[1] != endc && tstr[1] != ':') { + if (tstr[1] == 's') { + /* + * Use the char (if any) at tstr[2] + * as the word separator. + */ + VarPattern pattern; + + if (tstr[2] != endc && + (tstr[3] == endc || tstr[3] == ':')) { + /* ":ts" or + * ":ts:" */ + parsestate.varSpace = tstr[2]; + cp = tstr + 3; + } else if (tstr[2] == endc || tstr[2] == ':') { + /* ":ts" or ":ts:" */ + parsestate.varSpace = 0; /* no separator */ + cp = tstr + 2; + } else if (tstr[2] == '\\') { + const char *xp = &tstr[3]; + int base = 8; /* assume octal */ + + switch (tstr[3]) { + case 'n': + parsestate.varSpace = '\n'; + cp = tstr + 4; + break; + case 't': + parsestate.varSpace = '\t'; + cp = tstr + 4; + break; + case 'x': + base = 16; + xp++; + goto get_numeric; + case '0': + base = 0; + goto get_numeric; + default: + if (isdigit((unsigned char)tstr[3])) { + + get_numeric: + parsestate.varSpace = + strtoul(xp, &ep, base); + if (*ep != ':' && *ep != endc) + goto bad_modifier; + cp = ep; + } else { + /* + * ":ts". + */ + goto bad_modifier; + } + break; + } + } else { + /* + * Found ":ts". + */ + goto bad_modifier; + } + + termc = *cp; + + /* + * We cannot be certain that VarModify + * will be used - even if there is a + * subsequent modifier, so do a no-op + * VarSubstitute now to for str to be + * re-expanded without the spaces. + */ + pattern.flags = VAR_SUB_ONE; + pattern.lhs = pattern.rhs = "\032"; + pattern.leftLen = pattern.rightLen = 1; + + newStr = VarModify(ctxt, &parsestate, nstr, + VarSubstitute, + &pattern); + } else if (tstr[2] == endc || tstr[2] == ':') { + /* + * Check for two-character options: + * ":tu", ":tl" + */ + if (tstr[1] == 'A') { /* absolute path */ + newStr = VarModify(ctxt, &parsestate, nstr, + VarRealpath, NULL); + cp = tstr + 2; + termc = *cp; + } else if (tstr[1] == 'u') { + char *dp = bmake_strdup(nstr); + for (newStr = dp; *dp; dp++) + *dp = toupper((unsigned char)*dp); + cp = tstr + 2; + termc = *cp; + } else if (tstr[1] == 'l') { + char *dp = bmake_strdup(nstr); + for (newStr = dp; *dp; dp++) + *dp = tolower((unsigned char)*dp); + cp = tstr + 2; + termc = *cp; + } else if (tstr[1] == 'W' || tstr[1] == 'w') { + parsestate.oneBigWord = (tstr[1] == 'W'); + newStr = nstr; + cp = tstr + 2; + termc = *cp; + } else { + /* Found ":t:" or + * ":t". */ + goto bad_modifier; + } + } else { + /* + * Found ":t". + */ + goto bad_modifier; + } + } else { + /* + * Found ":t" or ":t:". + */ + goto bad_modifier; + } + break; + } + case 'N': + case 'M': + { + char *pattern; + const char *endpat; /* points just after end of pattern */ + char *cp2; + Boolean copy; /* pattern should be, or has been, copied */ + Boolean needSubst; + int nest; + + copy = FALSE; + needSubst = FALSE; + nest = 1; + /* + * In the loop below, ignore ':' unless we are at + * (or back to) the original brace level. + * XXX This will likely not work right if $() and ${} + * are intermixed. + */ + for (cp = tstr + 1; + *cp != '\0' && !(*cp == ':' && nest == 1); + cp++) + { + if (*cp == '\\' && + (cp[1] == ':' || + cp[1] == endc || cp[1] == startc)) { + if (!needSubst) { + copy = TRUE; + } + cp++; + continue; + } + if (*cp == '$') { + needSubst = TRUE; + } + if (*cp == '(' || *cp == '{') + ++nest; + if (*cp == ')' || *cp == '}') { + --nest; + if (nest == 0) + break; + } + } + termc = *cp; + endpat = cp; + if (copy) { + /* + * Need to compress the \:'s out of the pattern, so + * allocate enough room to hold the uncompressed + * pattern (note that cp started at tstr+1, so + * cp - tstr takes the null byte into account) and + * compress the pattern into the space. + */ + pattern = bmake_malloc(cp - tstr); + for (cp2 = pattern, cp = tstr + 1; + cp < endpat; + cp++, cp2++) + { + if ((*cp == '\\') && (cp+1 < endpat) && + (cp[1] == ':' || cp[1] == endc)) { + cp++; + } + *cp2 = *cp; + } + *cp2 = '\0'; + endpat = cp2; + } else { + /* + * Either Var_Subst or VarModify will need a + * nul-terminated string soon, so construct one now. + */ + pattern = bmake_strndup(tstr+1, endpat - (tstr + 1)); + } + if (needSubst) { + /* + * pattern contains embedded '$', so use Var_Subst to + * expand it. + */ + cp2 = pattern; + pattern = Var_Subst(NULL, cp2, ctxt, flags | VARF_WANTRES); + free(cp2); + } + if (DEBUG(VAR)) + fprintf(debug_file, "Pattern[%s] for [%s] is [%s]\n", + v->name, nstr, pattern); + if (*tstr == 'M') { + newStr = VarModify(ctxt, &parsestate, nstr, VarMatch, + pattern); + } else { + newStr = VarModify(ctxt, &parsestate, nstr, VarNoMatch, + pattern); + } + free(pattern); + break; + } + case 'S': + { + VarPattern pattern; + Var_Parse_State tmpparsestate; + + pattern.flags = 0; + tmpparsestate = parsestate; + delim = tstr[1]; + tstr += 2; + + /* + * If pattern begins with '^', it is anchored to the + * start of the word -- skip over it and flag pattern. + */ + if (*tstr == '^') { + pattern.flags |= VAR_MATCH_START; + tstr += 1; + } + + cp = tstr; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, + &pattern.flags, + &pattern.leftLen, + NULL)) == NULL) + goto cleanup; + + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, NULL, + &pattern.rightLen, + &pattern)) == NULL) + goto cleanup; + + /* + * Check for global substitution. If 'g' after the final + * delimiter, substitution is global and is marked that + * way. + */ + for (;; cp++) { + switch (*cp) { + case 'g': + pattern.flags |= VAR_SUB_GLOBAL; + continue; + case '1': + pattern.flags |= VAR_SUB_ONE; + continue; + case 'W': + tmpparsestate.oneBigWord = TRUE; + continue; + } + break; + } + + termc = *cp; + newStr = VarModify(ctxt, &tmpparsestate, nstr, + VarSubstitute, + &pattern); + + /* + * Free the two strings. + */ + free(UNCONST(pattern.lhs)); + free(UNCONST(pattern.rhs)); + delim = '\0'; + break; + } + case '?': + { + VarPattern pattern; + Boolean value; + int cond_rc; + int lhs_flags, rhs_flags; + + /* find ':', and then substitute accordingly */ + if (flags & VARF_WANTRES) { + cond_rc = Cond_EvalExpression(NULL, v->name, &value, 0, FALSE); + if (cond_rc == COND_INVALID) { + lhs_flags = rhs_flags = VAR_NOSUBST; + } else if (value) { + lhs_flags = 0; + rhs_flags = VAR_NOSUBST; + } else { + lhs_flags = VAR_NOSUBST; + rhs_flags = 0; + } + } else { + /* we are just consuming and discarding */ + cond_rc = value = 0; + lhs_flags = rhs_flags = VAR_NOSUBST; + } + pattern.flags = 0; + + cp = ++tstr; + delim = ':'; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, &lhs_flags, + &pattern.leftLen, + NULL)) == NULL) + goto cleanup; + + /* BROPEN or PROPEN */ + delim = endc; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, flags, + &cp, delim, &rhs_flags, + &pattern.rightLen, + NULL)) == NULL) + goto cleanup; + + termc = *--cp; + delim = '\0'; + if (cond_rc == COND_INVALID) { + Error("Bad conditional expression `%s' in %s?%s:%s", + v->name, v->name, pattern.lhs, pattern.rhs); + goto cleanup; + } + + if (value) { + newStr = UNCONST(pattern.lhs); + free(UNCONST(pattern.rhs)); + } else { + newStr = UNCONST(pattern.rhs); + free(UNCONST(pattern.lhs)); + } + if (v->flags & VAR_JUNK) { + v->flags |= VAR_KEEP; + } + break; + } +#ifndef NO_REGEX + case 'C': + { + VarREPattern pattern; + char *re; + int error; + Var_Parse_State tmpparsestate; + + pattern.flags = 0; + tmpparsestate = parsestate; + delim = tstr[1]; + tstr += 2; + + cp = tstr; + + if ((re = VarGetPattern(ctxt, &parsestate, flags, &cp, delim, + NULL, NULL, NULL)) == NULL) + goto cleanup; + + if ((pattern.replace = VarGetPattern(ctxt, &parsestate, + flags, &cp, delim, NULL, + NULL, NULL)) == NULL){ + free(re); + goto cleanup; + } + + for (;; cp++) { + switch (*cp) { + case 'g': + pattern.flags |= VAR_SUB_GLOBAL; + continue; + case '1': + pattern.flags |= VAR_SUB_ONE; + continue; + case 'W': + tmpparsestate.oneBigWord = TRUE; + continue; + } + break; + } + + termc = *cp; + + error = regcomp(&pattern.re, re, REG_EXTENDED); + free(re); + if (error) { + *lengthPtr = cp - start + 1; + VarREError(error, &pattern.re, "RE substitution error"); + free(pattern.replace); + goto cleanup; + } + + pattern.nsub = pattern.re.re_nsub + 1; + if (pattern.nsub < 1) + pattern.nsub = 1; + if (pattern.nsub > 10) + pattern.nsub = 10; + pattern.matches = bmake_malloc(pattern.nsub * + sizeof(regmatch_t)); + newStr = VarModify(ctxt, &tmpparsestate, nstr, + VarRESubstitute, + &pattern); + regfree(&pattern.re); + free(pattern.replace); + free(pattern.matches); + delim = '\0'; + break; + } +#endif + case 'q': + case 'Q': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarQuote(nstr, modifier == 'q'); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'T': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarTail, + NULL); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'H': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarHead, + NULL); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'E': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarSuffix, + NULL); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'R': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarModify(ctxt, &parsestate, nstr, VarRoot, + NULL); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; + case 'r': + cp = tstr + 1; /* make sure it is set */ + if (STRMOD_MATCHX(tstr, "range", 5)) { + int n; + + if (tstr[5] == '=') { + n = strtoul(&tstr[6], &ep, 10); + cp = ep; + } else { + n = 0; + cp = tstr + 5; + } + newStr = VarRange(nstr, n); + termc = *cp; + break; + } + goto default_case; + case 'O': + { + char otype; + + cp = tstr + 1; /* skip to the rest in any case */ + if (tstr[1] == endc || tstr[1] == ':') { + otype = 's'; + termc = *cp; + } else if ( (tstr[1] == 'x') && + (tstr[2] == endc || tstr[2] == ':') ) { + otype = tstr[1]; + cp = tstr + 2; + termc = *cp; + } else { + goto bad_modifier; + } + newStr = VarOrder(nstr, otype); + break; + } + case 'u': + if (tstr[1] == endc || tstr[1] == ':') { + newStr = VarUniq(nstr); + cp = tstr + 1; + termc = *cp; + break; + } + goto default_case; +#ifdef SUNSHCMD + case 's': + if (tstr[1] == 'h' && (tstr[2] == endc || tstr[2] == ':')) { + const char *emsg; + if (flags & VARF_WANTRES) { + newStr = Cmd_Exec(nstr, &emsg); + if (emsg) + Error(emsg, nstr); + } else + newStr = varNoError; + cp = tstr + 2; + termc = *cp; + break; + } + goto default_case; +#endif + default: + default_case: + { +#ifdef SYSVVARSUB + /* + * This can either be a bogus modifier or a System-V + * substitution command. + */ + VarPattern pattern; + Boolean eqFound; + + pattern.flags = 0; + eqFound = FALSE; + /* + * First we make a pass through the string trying + * to verify it is a SYSV-make-style translation: + * it must be: =) + */ + cp = tstr; + cnt = 1; + while (*cp != '\0' && cnt) { + if (*cp == '=') { + eqFound = TRUE; + /* continue looking for endc */ + } + else if (*cp == endc) + cnt--; + else if (*cp == startc) + cnt++; + if (cnt) + cp++; + } + if (*cp == endc && eqFound) { + + /* + * Now we break this sucker into the lhs and + * rhs. We must null terminate them of course. + */ + delim='='; + cp = tstr; + if ((pattern.lhs = VarGetPattern(ctxt, &parsestate, + flags, &cp, delim, &pattern.flags, + &pattern.leftLen, NULL)) == NULL) + goto cleanup; + delim = endc; + if ((pattern.rhs = VarGetPattern(ctxt, &parsestate, + flags, &cp, delim, NULL, &pattern.rightLen, + &pattern)) == NULL) + goto cleanup; + + /* + * SYSV modifications happen through the whole + * string. Note the pattern is anchored at the end. + */ + termc = *--cp; + delim = '\0'; + if (pattern.leftLen == 0 && *nstr == '\0') { + newStr = nstr; /* special case */ + } else { + newStr = VarModify(ctxt, &parsestate, nstr, + VarSYSVMatch, + &pattern); + } + free(UNCONST(pattern.lhs)); + free(UNCONST(pattern.rhs)); + } else +#endif + { + Error("Unknown modifier '%c'", *tstr); + for (cp = tstr+1; + *cp != ':' && *cp != endc && *cp != '\0'; + cp++) + continue; + termc = *cp; + newStr = var_Error; + } + } + } + if (DEBUG(VAR)) { + fprintf(debug_file, "Result[%s] of :%c is \"%s\"\n", + v->name, modifier, newStr); + } + + if (newStr != nstr) { + if (*freePtr) { + free(nstr); + *freePtr = NULL; + } + nstr = newStr; + if (nstr != var_Error && nstr != varNoError) { + *freePtr = nstr; + } + } + if (termc == '\0' && endc != '\0') { + Error("Unclosed variable specification (expecting '%c') for \"%s\" (value \"%s\") modifier %c", endc, v->name, nstr, modifier); + } else if (termc == ':') { + cp++; + } + tstr = cp; + } + out: + *lengthPtr = tstr - start; + return (nstr); + + bad_modifier: + /* "{(" */ + Error("Bad modifier `:%.*s' for %s", (int)strcspn(tstr, ":)}"), tstr, + v->name); + + cleanup: + *lengthPtr = cp - start; + if (delim != '\0') + Error("Unclosed substitution for %s (%c missing)", + v->name, delim); + free(*freePtr); + *freePtr = NULL; + return (var_Error); +} + +/*- + *----------------------------------------------------------------------- + * Var_Parse -- + * Given the start of a variable invocation, extract the variable + * name and find its value, then modify it according to the + * specification. + * + * Input: + * str The string to parse + * ctxt The context for the variable + * flags VARF_UNDEFERR if undefineds are an error + * VARF_WANTRES if we actually want the result + * VARF_ASSIGN if we are in a := assignment + * lengthPtr OUT: The length of the specification + * freePtr OUT: Non-NULL if caller should free *freePtr + * + * Results: + * The (possibly-modified) value of the variable or var_Error if the + * specification is invalid. The length of the specification is + * placed in *lengthPtr (for invalid specifications, this is just + * 2...?). + * If *freePtr is non-NULL then it's a pointer that the caller + * should pass to free() to free memory used by the result. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +/* coverity[+alloc : arg-*4] */ +char * +Var_Parse(const char *str, GNode *ctxt, int flags, + int *lengthPtr, void **freePtr) +{ + const char *tstr; /* Pointer into str */ + Var *v; /* Variable in invocation */ + Boolean haveModifier;/* TRUE if have modifiers for the variable */ + char endc; /* Ending character when variable in parens + * or braces */ + char startc; /* Starting character when variable in parens + * or braces */ + int vlen; /* Length of variable name */ + const char *start; /* Points to original start of str */ + char *nstr; /* New string, used during expansion */ + Boolean dynamic; /* TRUE if the variable is local and we're + * expanding it in a non-local context. This + * is done to support dynamic sources. The + * result is just the invocation, unaltered */ + const char *extramodifiers; /* extra modifiers to apply first */ + char name[2]; + + *freePtr = NULL; + extramodifiers = NULL; + dynamic = FALSE; + start = str; + + startc = str[1]; + if (startc != PROPEN && startc != BROPEN) { + /* + * If it's not bounded by braces of some sort, life is much simpler. + * We just need to check for the first character and return the + * value if it exists. + */ + + /* Error out some really stupid names */ + if (startc == '\0' || strchr(")}:$", startc)) { + *lengthPtr = 1; + return var_Error; + } + name[0] = startc; + name[1] = '\0'; + + v = VarFind(name, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + if (v == NULL) { + *lengthPtr = 2; + + if ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL)) { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (str[1]) { + case '@': + return UNCONST("$(.TARGET)"); + case '%': + return UNCONST("$(.MEMBER)"); + case '*': + return UNCONST("$(.PREFIX)"); + case '!': + return UNCONST("$(.ARCHIVE)"); + } + } + /* + * Error + */ + return (flags & VARF_UNDEFERR) ? var_Error : varNoError; + } else { + haveModifier = FALSE; + tstr = &str[1]; + endc = str[1]; + } + } else { + Buffer buf; /* Holds the variable name */ + int depth = 1; + + endc = startc == PROPEN ? PRCLOSE : BRCLOSE; + Buf_Init(&buf, 0); + + /* + * Skip to the end character or a colon, whichever comes first. + */ + for (tstr = str + 2; *tstr != '\0'; tstr++) + { + /* + * Track depth so we can spot parse errors. + */ + if (*tstr == startc) { + depth++; + } + if (*tstr == endc) { + if (--depth == 0) + break; + } + if (depth == 1 && *tstr == ':') { + break; + } + /* + * A variable inside a variable, expand + */ + if (*tstr == '$') { + int rlen; + void *freeIt; + char *rval = Var_Parse(tstr, ctxt, flags, &rlen, &freeIt); + if (rval != NULL) { + Buf_AddBytes(&buf, strlen(rval), rval); + } + free(freeIt); + tstr += rlen - 1; + } + else + Buf_AddByte(&buf, *tstr); + } + if (*tstr == ':') { + haveModifier = TRUE; + } else if (*tstr == endc) { + haveModifier = FALSE; + } else { + /* + * If we never did find the end character, return NULL + * right now, setting the length to be the distance to + * the end of the string, since that's what make does. + */ + *lengthPtr = tstr - str; + Buf_Destroy(&buf, TRUE); + return (var_Error); + } + str = Buf_GetAll(&buf, &vlen); + + /* + * At this point, str points into newly allocated memory from + * buf, containing only the name of the variable. + * + * start and tstr point into the const string that was pointed + * to by the original value of the str parameter. start points + * to the '$' at the beginning of the string, while tstr points + * to the char just after the end of the variable name -- this + * will be '\0', ':', PRCLOSE, or BRCLOSE. + */ + + v = VarFind(str, ctxt, FIND_ENV | FIND_GLOBAL | FIND_CMD); + /* + * Check also for bogus D and F forms of local variables since we're + * in a local context and the name is the right length. + */ + if ((v == NULL) && (ctxt != VAR_CMD) && (ctxt != VAR_GLOBAL) && + (vlen == 2) && (str[1] == 'F' || str[1] == 'D') && + strchr("@%?*!<>", str[0]) != NULL) { + /* + * Well, it's local -- go look for it. + */ + name[0] = *str; + name[1] = '\0'; + v = VarFind(name, ctxt, 0); + + if (v != NULL) { + if (str[1] == 'D') { + extramodifiers = "H:"; + } + else { /* F */ + extramodifiers = "T:"; + } + } + } + + if (v == NULL) { + if (((vlen == 1) || + (((vlen == 2) && (str[1] == 'F' || str[1] == 'D')))) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + /* + * If substituting a local variable in a non-local context, + * assume it's for dynamic source stuff. We have to handle + * this specially and return the longhand for the variable + * with the dollar sign escaped so it makes it back to the + * caller. Only four of the local variables are treated + * specially as they are the only four that will be set + * when dynamic sources are expanded. + */ + switch (*str) { + case '@': + case '%': + case '*': + case '!': + dynamic = TRUE; + break; + } + } else if ((vlen > 2) && (*str == '.') && + isupper((unsigned char) str[1]) && + ((ctxt == VAR_CMD) || (ctxt == VAR_GLOBAL))) + { + int len; + + len = vlen - 1; + if ((strncmp(str, ".TARGET", len) == 0) || + (strncmp(str, ".ARCHIVE", len) == 0) || + (strncmp(str, ".PREFIX", len) == 0) || + (strncmp(str, ".MEMBER", len) == 0)) + { + dynamic = TRUE; + } + } + + if (!haveModifier) { + /* + * No modifiers -- have specification length so we can return + * now. + */ + *lengthPtr = tstr - start + 1; + if (dynamic) { + char *pstr = bmake_strndup(start, *lengthPtr); + *freePtr = pstr; + Buf_Destroy(&buf, TRUE); + return(pstr); + } else { + Buf_Destroy(&buf, TRUE); + return (flags & VARF_UNDEFERR) ? var_Error : varNoError; + } + } else { + /* + * Still need to get to the end of the variable specification, + * so kludge up a Var structure for the modifications + */ + v = bmake_malloc(sizeof(Var)); + v->name = UNCONST(str); + Buf_Init(&v->val, 1); + v->flags = VAR_JUNK; + Buf_Destroy(&buf, FALSE); + } + } else + Buf_Destroy(&buf, TRUE); + } + + if (v->flags & VAR_IN_USE) { + Fatal("Variable %s is recursive.", v->name); + /*NOTREACHED*/ + } else { + v->flags |= VAR_IN_USE; + } + /* + * Before doing any modification, we have to make sure the value + * has been fully expanded. If it looks like recursion might be + * necessary (there's a dollar sign somewhere in the variable's value) + * we just call Var_Subst to do any other substitutions that are + * necessary. Note that the value returned by Var_Subst will have + * been dynamically-allocated, so it will need freeing when we + * return. + */ + nstr = Buf_GetAll(&v->val, NULL); + if (strchr(nstr, '$') != NULL) { + nstr = Var_Subst(NULL, nstr, ctxt, flags); + *freePtr = nstr; + } + + v->flags &= ~VAR_IN_USE; + + if ((nstr != NULL) && (haveModifier || extramodifiers != NULL)) { + void *extraFree; + int used; + + extraFree = NULL; + if (extramodifiers != NULL) { + nstr = ApplyModifiers(nstr, extramodifiers, '(', ')', + v, ctxt, flags, &used, &extraFree); + } + + if (haveModifier) { + /* Skip initial colon. */ + tstr++; + + nstr = ApplyModifiers(nstr, tstr, startc, endc, + v, ctxt, flags, &used, freePtr); + tstr += used; + free(extraFree); + } else { + *freePtr = extraFree; + } + } + if (*tstr) { + *lengthPtr = tstr - start + 1; + } else { + *lengthPtr = tstr - start; + } + + if (v->flags & VAR_FROM_ENV) { + Boolean destroy = FALSE; + + if (nstr != Buf_GetAll(&v->val, NULL)) { + destroy = TRUE; + } else { + /* + * Returning the value unmodified, so tell the caller to free + * the thing. + */ + *freePtr = nstr; + } + VarFreeEnv(v, destroy); + } else if (v->flags & VAR_JUNK) { + /* + * Perform any free'ing needed and set *freePtr to NULL so the caller + * doesn't try to free a static pointer. + * If VAR_KEEP is also set then we want to keep str as is. + */ + if (!(v->flags & VAR_KEEP)) { + if (*freePtr) { + free(nstr); + *freePtr = NULL; + } + if (dynamic) { + nstr = bmake_strndup(start, *lengthPtr); + *freePtr = nstr; + } else { + nstr = (flags & VARF_UNDEFERR) ? var_Error : varNoError; + } + } + if (nstr != Buf_GetAll(&v->val, NULL)) + Buf_Destroy(&v->val, TRUE); + free(v->name); + free(v); + } + return (nstr); +} + +/*- + *----------------------------------------------------------------------- + * Var_Subst -- + * Substitute for all variables in the given string in the given context + * If flags & VARF_UNDEFERR, Parse_Error will be called when an undefined + * variable is encountered. + * + * Input: + * var Named variable || NULL for all + * str the string which to substitute + * ctxt the context wherein to find variables + * flags VARF_UNDEFERR if undefineds are an error + * VARF_WANTRES if we actually want the result + * VARF_ASSIGN if we are in a := assignment + * + * Results: + * The resulting string. + * + * Side Effects: + * None. The old string must be freed by the caller + *----------------------------------------------------------------------- + */ +char * +Var_Subst(const char *var, const char *str, GNode *ctxt, int flags) +{ + Buffer buf; /* Buffer for forming things */ + char *val; /* Value to substitute for a variable */ + int length; /* Length of the variable invocation */ + Boolean trailingBslash; /* variable ends in \ */ + void *freeIt = NULL; /* Set if it should be freed */ + static Boolean errorReported; /* Set true if an error has already + * been reported to prevent a plethora + * of messages when recursing */ + + Buf_Init(&buf, 0); + errorReported = FALSE; + trailingBslash = FALSE; + + while (*str) { + if (*str == '\n' && trailingBslash) + Buf_AddByte(&buf, ' '); + if (var == NULL && (*str == '$') && (str[1] == '$')) { + /* + * A dollar sign may be escaped either with another dollar sign. + * In such a case, we skip over the escape character and store the + * dollar sign into the buffer directly. + */ + if (save_dollars && (flags & VARF_ASSIGN)) + Buf_AddByte(&buf, *str); + str++; + Buf_AddByte(&buf, *str); + str++; + } else if (*str != '$') { + /* + * Skip as many characters as possible -- either to the end of + * the string or to the next dollar sign (variable invocation). + */ + const char *cp; + + for (cp = str++; *str != '$' && *str != '\0'; str++) + continue; + Buf_AddBytes(&buf, str - cp, cp); + } else { + if (var != NULL) { + int expand; + for (;;) { + if (str[1] == '\0') { + /* A trailing $ is kind of a special case */ + Buf_AddByte(&buf, str[0]); + str++; + expand = FALSE; + } else if (str[1] != PROPEN && str[1] != BROPEN) { + if (str[1] != *var || strlen(var) > 1) { + Buf_AddBytes(&buf, 2, str); + str += 2; + expand = FALSE; + } + else + expand = TRUE; + break; + } + else { + const char *p; + + /* + * Scan up to the end of the variable name. + */ + for (p = &str[2]; *p && + *p != ':' && *p != PRCLOSE && *p != BRCLOSE; p++) + if (*p == '$') + break; + /* + * A variable inside the variable. We cannot expand + * the external variable yet, so we try again with + * the nested one + */ + if (*p == '$') { + Buf_AddBytes(&buf, p - str, str); + str = p; + continue; + } + + if (strncmp(var, str + 2, p - str - 2) != 0 || + var[p - str - 2] != '\0') { + /* + * Not the variable we want to expand, scan + * until the next variable + */ + for (;*p != '$' && *p != '\0'; p++) + continue; + Buf_AddBytes(&buf, p - str, str); + str = p; + expand = FALSE; + } + else + expand = TRUE; + break; + } + } + if (!expand) + continue; + } + + val = Var_Parse(str, ctxt, flags, &length, &freeIt); + + /* + * When we come down here, val should either point to the + * value of this variable, suitably modified, or be NULL. + * Length should be the total length of the potential + * variable invocation (from $ to end character...) + */ + if (val == var_Error || val == varNoError) { + /* + * If performing old-time variable substitution, skip over + * the variable and continue with the substitution. Otherwise, + * store the dollar sign and advance str so we continue with + * the string... + */ + if (oldVars) { + str += length; + } else if ((flags & VARF_UNDEFERR) || val == var_Error) { + /* + * If variable is undefined, complain and skip the + * variable. The complaint will stop us from doing anything + * when the file is parsed. + */ + if (!errorReported) { + Parse_Error(PARSE_FATAL, + "Undefined variable \"%.*s\"",length,str); + } + str += length; + errorReported = TRUE; + } else { + Buf_AddByte(&buf, *str); + str += 1; + } + } else { + /* + * We've now got a variable structure to store in. But first, + * advance the string pointer. + */ + str += length; + + /* + * Copy all the characters from the variable value straight + * into the new string. + */ + length = strlen(val); + Buf_AddBytes(&buf, length, val); + trailingBslash = length > 0 && val[length - 1] == '\\'; + } + free(freeIt); + freeIt = NULL; + } + } + + return Buf_DestroyCompact(&buf); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetTail -- + * Return the tail from each of a list of words. Used to set the + * System V local variables. + * + * Input: + * file Filename to modify + * + * Results: + * The resulting string. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +#if 0 +char * +Var_GetTail(char *file) +{ + return(VarModify(file, VarTail, NULL)); +} + +/*- + *----------------------------------------------------------------------- + * Var_GetHead -- + * Find the leading components of a (list of) filename(s). + * XXX: VarHead does not replace foo by ., as (sun) System V make + * does. + * + * Input: + * file Filename to manipulate + * + * Results: + * The leading components. + * + * Side Effects: + * None. + * + *----------------------------------------------------------------------- + */ +char * +Var_GetHead(char *file) +{ + return(VarModify(file, VarHead, NULL)); +} +#endif + +/*- + *----------------------------------------------------------------------- + * Var_Init -- + * Initialize the module + * + * Results: + * None + * + * Side Effects: + * The VAR_CMD and VAR_GLOBAL contexts are created + *----------------------------------------------------------------------- + */ +void +Var_Init(void) +{ + VAR_INTERNAL = Targ_NewGN("Internal"); + VAR_GLOBAL = Targ_NewGN("Global"); + VAR_CMD = Targ_NewGN("Command"); + +} + + +void +Var_End(void) +{ +} + + +/****************** PRINT DEBUGGING INFO *****************/ +static void +VarPrintVar(void *vp) +{ + Var *v = (Var *)vp; + fprintf(debug_file, "%-16s = %s\n", v->name, Buf_GetAll(&v->val, NULL)); +} + +/*- + *----------------------------------------------------------------------- + * Var_Dump -- + * print all variables in a context + *----------------------------------------------------------------------- + */ +void +Var_Dump(GNode *ctxt) +{ + Hash_Search search; + Hash_Entry *h; + + for (h = Hash_EnumFirst(&ctxt->context, &search); + h != NULL; + h = Hash_EnumNext(&search)) { + VarPrintVar(Hash_GetValue(h)); + } +} diff --git a/usr.bin/man/man.1 b/usr.bin/man/man.1 new file mode 100644 index 0000000..49b3bf3 --- /dev/null +++ b/usr.bin/man/man.1 @@ -0,0 +1,269 @@ +.\" $NetBSD: man.1,v 1.29 2016/06/16 15:10:58 abhinav Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)man.1 8.2 (Berkeley) 1/2/94 +.\" +.Dd June 16, 2016 +.Dt MAN 1 +.Os +.Sh NAME +.Nm man +.Nd display the on-line manual pages +.Pq aka Dq Em man pages +.Sh SYNOPSIS +.Nm +.Oo Fl acw Ns \&| Ns Fl h Oc +.Op Fl C Ar file +.Op Fl M Ar path +.Op Fl m Ar path +.Op Fl S Ar srch +.Oo +.Op Fl s +.Ar section +.Oc +.Ar name Ar ... +.Nm +.Op Fl C Ar file +.Fl f +.Ar command Ar ... +.Nm +.Op Fl C Ar file +.Fl k +.Ar keyword Ar ... +.Nm +.Fl p +.Sh DESCRIPTION +The +.Nm +utility displays the manual pages named on the command line. +Its options are as follows: +.Bl -tag -width indent +.It Fl a +Display all of the man pages for a specified +.Ar section +and +.Ar name +combination. +(Normally, only the first man page found is displayed.) +.It Fl C +Use the specified +.Ar file +instead of the default configuration file. +This permits users to configure their own man environment. +See +.Xr man.conf 5 +for a description of the contents of this file. +.It Fl c +Copy the man page to the standard output instead of using +.Xr more 1 +to paginate it. +This is done by default if the standard output is not a terminal device. +.It Fl f +Synonym for +.Xr whatis 1 . +It searches man pages for +.Ar command +in their names and displays header lines from all matching pages. +.It Fl h +Display only the +.Dq Tn SYNOPSIS +lines of the requested man pages. +For commands, this is typically the command line usage information. +For library functions, this usually contains the required include +files and function prototypes. +.It Fl k +Search man pages for +.Ar keyword Ns Pq s , +in the same manner as +.Xr apropos 1 . +.It Fl M +Override the list of standard directories which +.Nm +searches for man pages. +The supplied +.Ar path +must be a colon +.Pq Dq \&: +separated list of directories. +This search path may also be set using the environment variable +.Ev MANPATH . +The subdirectories to be searched, and their search order, +is specified by the +.Dq _subdir +line in the +.Nm +configuration file. +.It Fl m +Augment the list of standard directories which +.Nm +searches for man pages. +The supplied +.Ar path +must be a colon +.Pq Dq \&: +separated list of directories. +These directories will be searched before the standard directories or +the directories specified using the +.Fl M +option or the +.Ev MANPATH +environment variable. +The subdirectories to be searched, and their search order, +is specified by the +.Dq _subdir +line in the +.Nm +configuration file. +.It Fl p +Print the search path for the manual pages. +.It Fl s +Restrict the directories that +.Nm +will search to the specified section. +The +.Nm +configuration file (see +.Xr man.conf 5 ) +specifies the possible +.Ar section +values that are currently available. +.It Fl S +Display only man pages that have the specified string in the directory +part of their filenames. +This allows the man page search process criteria to be +narrowed without having to change the MANPATH or +.Dq _default +variables. +.It Fl w +List the pathnames of the man pages which +.Nm +would display for the specified +.Ar section +and +.Ar name +combination. +.El +.Pp +If the +.Ql Fl s +option is not specified, +there is more than one argument, +the +.Ql Fl k +option is not used, and the first argument is a valid section, then that +argument will be used as if specified by the +.Ql Fl s +option. +.Pp +If +.Ar name +is given with a full path (beginning with +.Ql Pa \&/ ) +or a relative path that begins with +.Ql Pa .\&/ +or +.Ql Pa .\&./ , +then +.Nm +interprets it as a file specification, so that you can do +.Nm +.Cm ./foo.5 +or even +.Nm +.Cm /cd/foo/bar.1.gz . +If +.Ar name +contains +.Ql Pa / +but does not match one of the above cases, then the +search path is used; this allows you to request +machine-specific man pages, such as +.Nm Cm vax/boot . +.Sh ENVIRONMENT +.Bl -tag -width MANPATHX +.It Ev MACHINE +As some man pages are intended only for specific architectures, +.Nm +searches any subdirectories, +with the same name as the current architecture, +in every directory which it searches. +Machine specific areas are checked before general areas. +The current machine type may be overridden by setting the environment +variable +.Ev MACHINE +to the name of a specific architecture. +Machine-specific man pages may also be requested by +prepending the relevant subdirectory name to the page name, +separated by +.Ql Pa \&/ . +.It Ev MANPATH +The standard search path used by +.Nm +may be overridden by specifying a path in the +.Ev MANPATH +environment variable. +The format of the path is a colon +.Pq Dq \&: +separated list of directories. +The subdirectories to be searched as well as their search order +is specified by the +.Dq _subdir +line in the +.Nm +configuration file. +.It Ev PAGER +The pagination command used for writing the output. +If the +.Ev PAGER +environment variable is null or not set, the standard pagination program +.Xr more 1 +will be used. +.El +.Sh FILES +.Bl -hang -width /etc/man.conf -compact +.It Pa /etc/man.conf +default man configuration file. +.El +.Sh SEE ALSO +.Xr apropos 1 , +.Xr whatis 1 , +.Xr whereis 1 , +.Xr man.conf 5 , +.Xr mdoc 7 , +.Xr mdoc.samples 7 +.Sh STANDARDS +.Nm +conforms to +.St -xcu5 . +.\"and is expected to conform to +.\".St -p1003.2-?? . +.Sh BUGS +The on-line man pages are, by necessity, forgiving toward stupid +display devices, causing a few man pages to be not as nicely formatted +as their typeset counterparts. diff --git a/usr.bin/man/man.c b/usr.bin/man/man.c new file mode 100644 index 0000000..cba36bc --- /dev/null +++ b/usr.bin/man/man.c @@ -0,0 +1,1077 @@ +/* $NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994, 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994, 1995\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)man.c 8.17 (Berkeley) 1/31/95"; +#else +__RCSID("$NetBSD: man.c,v 1.67 2018/06/15 20:16:35 mrg Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "manconf.h" +#include "pathnames.h" + +#ifndef MAN_DEBUG +#define MAN_DEBUG 0 /* debug path output */ +#endif + +/* + * manstate: structure collecting the current global state so we can + * easily identify it and pass it to helper functions in one arg. + */ +struct manstate { + /* command line flags */ + int all; /* -a: show all matches rather than first */ + int cat; /* -c: do not use a pager */ + char *conffile; /* -C: use alternate config file */ + int how; /* -h: show SYNOPSIS only */ + char *manpath; /* -M: alternate MANPATH */ + char *addpath; /* -m: add these dirs to front of manpath */ + char *pathsearch; /* -S: path of man must contain this string */ + char *sectionname; /* -s: limit search to a given man section */ + int where; /* -w: just show paths of all matching files */ + int getpath; /* -p: print the path of directories containing man pages */ + + /* important tags from the config file */ + TAG *defaultpath; /* _default: default MANPATH */ + TAG *subdirs; /* _subdir: default subdir search list */ + TAG *suffixlist; /* _suffix: for files that can be cat()'d */ + TAG *buildlist; /* _build: for files that must be built */ + + /* tags for internal use */ + TAG *intmp; /* _intmp: tmp files we must cleanup */ + TAG *missinglist; /* _missing: pages we couldn't find */ + TAG *mymanpath; /* _new_path: final version of MANPATH */ + TAG *section; /* : tag for m.sectionname */ + + /* other misc stuff */ + const char *pager; /* pager to use */ + size_t pagerlen; /* length of the above */ + const char *machine; /* machine */ + const char *machclass; /* machine class */ +}; + +/* + * prototypes + */ +static void build_page(const char *, char **, struct manstate *); +static void cat(const char *); +static const char *check_pager(const char *); +static int cleanup(void); +static void how(const char *); +static void jump(char **, const char *, const char *) __dead; +static int manual(char *, struct manstate *, glob_t *); +static void onsig(int) __dead; +static void usage(void) __dead; +static void addpath(struct manstate *, const char *, size_t, const char *); +static const char *getclass(const char *); +static void printmanpath(struct manstate *); + +/* + * main function + */ +int +main(int argc, char **argv) +{ + static struct manstate m; + struct utsname utsname; + int ch, abs_section, found; + ENTRY *esubd, *epath; + char *p, **ap, *cmd; + size_t len; + glob_t pg; + + setprogname(argv[0]); + setlocale(LC_ALL, ""); + /* + * parse command line... + */ + while ((ch = getopt(argc, argv, "-aC:cfhkM:m:P:ps:S:w")) != -1) + switch (ch) { + case 'a': + m.all = 1; + break; + case 'C': + m.conffile = optarg; + break; + case 'c': + case '-': /* XXX: '-' is a deprecated version of '-c' */ + m.cat = 1; + break; + case 'h': + m.how = 1; + break; + case 'm': + m.addpath = optarg; + break; + case 'M': + case 'P': /* -P for backward compatibility */ + if ((m.manpath = strdup(optarg)) == NULL) + err(EXIT_FAILURE, "malloc failed"); + break; + case 'p': + m.getpath = 1; + break; + /* + * The -f and -k options are backward compatible, + * undocumented ways of calling whatis(1) and apropos(1). + */ + case 'f': + jump(argv, "-f", "whatis"); + /* NOTREACHED */ + case 'k': + jump(argv, "-k", "apropos"); + /* NOTREACHED */ + case 's': + if (m.sectionname != NULL) + usage(); + m.sectionname = optarg; + break; + case 'S': + m.pathsearch = optarg; + break; + case 'w': + m.all = m.where = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!m.getpath && !argc) + usage(); + + /* + * read the configuration file and collect any other information + * we will need (machine type, pager, section [if specified + * without '-s'], and MANPATH through the environment). + */ + config(m.conffile); /* exits on error ... */ + + if ((m.machine = getenv("MACHINE")) == NULL) { + if (uname(&utsname) == -1) + err(EXIT_FAILURE, "uname"); + m.machine = utsname.machine; + } + + m.machclass = getclass(m.machine); + + if (!m.cat && !m.how && !m.where) { /* if we need a pager ... */ + if (!isatty(STDOUT_FILENO)) { + m.cat = 1; + } else { + if ((m.pager = getenv("PAGER")) != NULL && + m.pager[0] != '\0') + m.pager = check_pager(m.pager); + else + m.pager = _PATH_PAGER; + m.pagerlen = strlen(m.pager); + } + } + + /* do we need to set m.section to a non-null value? */ + if (m.sectionname) { + + m.section = gettag(m.sectionname, 0); /* -s must be a section */ + if (m.section == NULL) + errx(EXIT_FAILURE, "unknown section: %s", m.sectionname); + + } else if (argc > 1) { + + m.section = gettag(*argv, 0); /* might be a section? */ + if (m.section) { + argv++; + argc--; + } + + } + + if (m.manpath == NULL) + m.manpath = getenv("MANPATH"); /* note: -M overrides getenv */ + + + /* + * get default values from config file, plus create the tags we + * use for keeping internal state. make sure all our mallocs + * go through. + */ + /* from cfg file */ + m.defaultpath = gettag("_default", 1); + m.subdirs = gettag("_subdir", 1); + m.suffixlist = gettag("_suffix", 1); + m.buildlist = gettag("_build", 1); + /* internal use */ + m.mymanpath = gettag("_new_path", 1); + m.missinglist = gettag("_missing", 1); + m.intmp = gettag("_intmp", 1); + if (!m.defaultpath || !m.subdirs || !m.suffixlist || !m.buildlist || + !m.mymanpath || !m.missinglist || !m.intmp) + errx(EXIT_FAILURE, "malloc failed"); + + /* + * are we using a section whose elements are all absolute paths? + * (we only need to look at the first entry on the section list, + * as config() will ensure that any additional entries will match + * the first one.) + */ + abs_section = (m.section != NULL && + !TAILQ_EMPTY(&m.section->entrylist) && + *(TAILQ_FIRST(&m.section->entrylist)->s) == '/'); + + /* + * now that we have all the data we need, we must determine the + * manpath we are going to use to find the requested entries using + * the following steps... + * + * [1] if the user specified a section and that section's elements + * from the config file are all absolute paths, then we override + * defaultpath and -M/MANPATH with the section's absolute paths. + */ + if (abs_section) { + m.manpath = NULL; /* ignore -M/MANPATH */ + m.defaultpath = m.section; /* overwrite _default path */ + m.section = NULL; /* promoted to defaultpath */ + } + + /* + * [2] section can now only be non-null if the user asked for + * a section and that section's elements did not have + * absolute paths. in this case we use the section's + * elements to override _subdir from the config file. + * + * after this step, we are done processing "m.section"... + */ + if (m.section) + m.subdirs = m.section; + + /* + * [3] we need to setup the path we want to use (m.mymanpath). + * if the user gave us a path (m.manpath) use it, otherwise + * go with the default. in either case we need to append + * the subdir and machine spec to each element of the path. + * + * for absolute section paths that come from the config file, + * we only append the subdir spec if the path ends in + * a '/' --- elements that do not end in '/' are assumed to + * not have subdirectories. this is mainly for backward compat, + * but it allows non-subdir configs like: + * sect3 /usr/share/man/{old/,}cat3 + * doc /usr/{pkg,share}/doc/{sendmail/op,sendmail/intro} + * + * note that we try and be careful to not put double slashes + * in the path (e.g. we want /usr/share/man/man1, not + * /usr/share/man//man1) because "more" will put the filename + * we generate in its prompt and the double slashes look ugly. + */ + if (m.manpath) { + + /* note: strtok is going to destroy m.manpath */ + for (p = strtok(m.manpath, ":") ; p ; p = strtok(NULL, ":")) { + len = strlen(p); + if (len < 1) + continue; + TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) + addpath(&m, p, len, esubd->s); + } + + } else { + + TAILQ_FOREACH(epath, &m.defaultpath->entrylist, q) { + /* handle trailing "/" magic here ... */ + if (abs_section && epath->s[epath->len - 1] != '/') { + addpath(&m, "", 1, epath->s); + continue; + } + + TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) + addpath(&m, epath->s, epath->len, esubd->s); + } + + } + + /* + * [4] finally, prepend the "-m" m.addpath to mymanpath if it + * was specified. subdirs and machine are always applied to + * m.addpath. + */ + if (m.addpath) { + + /* note: strtok is going to destroy m.addpath */ + for (p = strtok(m.addpath, ":") ; p ; p = strtok(NULL, ":")) { + len = strlen(p); + if (len < 1) + continue; + TAILQ_FOREACH(esubd, &m.subdirs->entrylist, q) + addpath(&m, p, len, esubd->s); + } + + } + + if (m.getpath) + printmanpath(&m); + + /* + * now m.mymanpath is complete! + */ +#if MAN_DEBUG + printf("mymanpath:\n"); + TAILQ_FOREACH(epath, &m.mymanpath->entrylist, q) { + printf("\t%s\n", epath->s); + } +#endif + + /* + * start searching for matching files and format them if necessary. + * setup an interrupt handler so that we can ensure that temporary + * files go away. + */ + (void)signal(SIGINT, onsig); + (void)signal(SIGHUP, onsig); + (void)signal(SIGPIPE, onsig); + + memset(&pg, 0, sizeof(pg)); + for (found = 0; *argv; ++argv) + if (manual(*argv, &m, &pg)) { + found = 1; + } + + /* if nothing found, we're done. */ + if (!found) { + (void)cleanup(); + exit(EXIT_FAILURE); + } + + /* + * handle the simple display cases first (m.cat, m.how, m.where) + */ + if (m.cat) { + for (ap = pg.gl_pathv; *ap != NULL; ++ap) { + if (**ap == '\0') + continue; + cat(*ap); + } + exit(cleanup()); + } + if (m.how) { + for (ap = pg.gl_pathv; *ap != NULL; ++ap) { + if (**ap == '\0') + continue; + how(*ap); + } + exit(cleanup()); + } + if (m.where) { + for (ap = pg.gl_pathv; *ap != NULL; ++ap) { + if (**ap == '\0') + continue; + (void)printf("%s\n", *ap); + } + exit(cleanup()); + } + + /* + * normal case - we display things in a single command, so + * build a list of things to display. first compute total + * length of buffer we will need so we can malloc it. + */ + for (ap = pg.gl_pathv, len = m.pagerlen + 1; *ap != NULL; ++ap) { + if (**ap == '\0') + continue; + len += strlen(*ap) + 1; + } + if ((cmd = malloc(len)) == NULL) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + + /* now build the command string... */ + p = cmd; + len = m.pagerlen; + memcpy(p, m.pager, len); + p += len; + *p++ = ' '; + for (ap = pg.gl_pathv; *ap != NULL; ++ap) { + if (**ap == '\0') + continue; + len = strlen(*ap); + memcpy(p, *ap, len); + p += len; + *p++ = ' '; + } + *--p = '\0'; + + /* Use system(3) in case someone's pager is "pager arg1 arg2". */ + (void)system(cmd); + + exit(cleanup()); +} + +static int +manual_find_literalfile(struct manstate *mp, char **pv) +{ + ENTRY *suffix; + int found; + char buf[MAXPATHLEN]; + const char *p; + int suflen; + + found = 0; + + /* + * Expand both '*' and suffix to force an actual + * match via fnmatch(3). Since the only match in pg + * is the literal file, the match is genuine. + */ + + TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { + for (p = suffix->s, suflen = 0; + *p != '\0' && !isspace((unsigned char)*p); + ++p) + ++suflen; + if (*p == '\0') + continue; + + (void)snprintf(buf, sizeof(buf), "*%.*s", suflen, suffix->s); + + if (!fnmatch(buf, *pv, 0)) { + if (!mp->where) + build_page(p + 1, pv, mp); + found = 1; + break; + } + } + + return found; +} + +static int +manual_find_buildkeyword(const char *prefix, const char *escpage, + struct manstate *mp, char **pv) +{ + ENTRY *suffix; + int found; + char buf[MAXPATHLEN]; + const char *p; + int suflen; + + found = 0; + /* Try the _build keywords next. */ + TAILQ_FOREACH(suffix, &mp->buildlist->entrylist, q) { + for (p = suffix->s, suflen = 0; + *p != '\0' && !isspace((unsigned char)*p); + ++p) + ++suflen; + if (*p == '\0') + continue; + + (void)snprintf(buf, sizeof(buf), "%s%s%.*s", + prefix, escpage, suflen, suffix->s); + if (!fnmatch(buf, *pv, 0)) { + if (!mp->where) + build_page(p + 1, pv, mp); + found = 1; + break; + } + } + + return found; +} + +/* + * manual -- + * Search the manuals for the pages. + */ +static int +manual(char *page, struct manstate *mp, glob_t *pg) +{ + ENTRY *suffix, *mdir; + int anyfound, error, found; + size_t cnt; + char *p, buf[MAXPATHLEN], *escpage, *eptr; + static const char escglob[] = "\\~?*{}[]"; + + anyfound = 0; + + /* + * Fixup page which may contain glob(3) special characters, e.g. + * the famous "No man page for [" FAQ. + */ + if ((escpage = malloc((2 * strlen(page)) + 1)) == NULL) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + + p = page; + eptr = escpage; + + while (*p) { + if (strchr(escglob, *p) != NULL) { + *eptr++ = '\\'; + *eptr++ = *p++; + } else + *eptr++ = *p++; + } + + *eptr = '\0'; + + /* + * If 'page' is given with an absolute path, + * or a relative path explicitly beginning with "./" + * or "../", then interpret it as a file specification. + */ + if ((page[0] == '/') + || (page[0] == '.' && page[1] == '/') + || (page[0] == '.' && page[1] == '.' && page[2] == '/') + ) { + /* check if file actually exists */ + (void)strlcpy(buf, escpage, sizeof(buf)); + error = glob(buf, GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg); + if (error != 0) { + if (error == GLOB_NOMATCH) { + goto notfound; + } else { + errx(EXIT_FAILURE, "glob failed"); + } + } + + if (pg->gl_matchc == 0) + goto notfound; + + /* literal file only yields one match */ + cnt = pg->gl_pathc - pg->gl_matchc; + + if (manual_find_literalfile(mp, &pg->gl_pathv[cnt])) { + anyfound = 1; + } else { + /* It's not a man page, forget about it. */ + *pg->gl_pathv[cnt] = '\0'; + } + + notfound: + if (!anyfound) { + if (addentry(mp->missinglist, page, 0) < 0) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + } + free(escpage); + return anyfound; + } + + /* For each man directory in mymanpath ... */ + TAILQ_FOREACH(mdir, &mp->mymanpath->entrylist, q) { + + /* + * use glob(3) to look in the filesystem for matching files. + * match any suffix here, as we will check that later. + */ + (void)snprintf(buf, sizeof(buf), "%s/%s.*", mdir->s, escpage); + if ((error = glob(buf, + GLOB_APPEND | GLOB_BRACE | GLOB_NOSORT, NULL, pg)) != 0) { + if (error == GLOB_NOMATCH) + continue; + else { + warn("globbing"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + } + if (pg->gl_matchc == 0) + continue; + + /* + * start going through the matches glob(3) just found and + * use m.pathsearch (if present) to filter out pages we + * don't want. then verify the suffix is valid, and build + * the page if we have a _build suffix. + */ + for (cnt = pg->gl_pathc - pg->gl_matchc; + cnt < pg->gl_pathc; ++cnt) { + + /* filter on directory path name */ + if (mp->pathsearch) { + p = strstr(pg->gl_pathv[cnt], mp->pathsearch); + if (!p || strchr(p, '/') == NULL) { + *pg->gl_pathv[cnt] = '\0'; /* zap! */ + continue; + } + } + + /* + * Try the _suffix keywords first. + * + * XXX + * Older versions of man.conf didn't have the _suffix + * keywords, it was assumed that everything was a .0. + * We just test for .0 first, it's fast and probably + * going to hit. + */ + (void)snprintf(buf, sizeof(buf), "*/%s.0", escpage); + if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) + goto next; + + found = 0; + TAILQ_FOREACH(suffix, &mp->suffixlist->entrylist, q) { + (void)snprintf(buf, + sizeof(buf), "*/%s%s", escpage, + suffix->s); + if (!fnmatch(buf, pg->gl_pathv[cnt], 0)) { + found = 1; + break; + } + } + if (found) + goto next; + + /* Try the _build keywords next. */ + found = manual_find_buildkeyword("*/", escpage, + mp, &pg->gl_pathv[cnt]); + if (found) { +next: anyfound = 1; + if (!mp->all) { + /* Delete any other matches. */ + while (++cnt< pg->gl_pathc) + *pg->gl_pathv[cnt] = '\0'; + break; + } + continue; + } + + /* It's not a man page, forget about it. */ + *pg->gl_pathv[cnt] = '\0'; + } + + if (anyfound && !mp->all) + break; + } + + /* If not found, enter onto the missing list. */ + if (!anyfound) { + if (addentry(mp->missinglist, page, 0) < 0) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + } + + free(escpage); + return anyfound; +} + +/* + * A do-nothing counterpart to fmtcheck(3) that only supplies the + * __format_arg marker. Actual fmtcheck(3) call is done once in + * config(). + */ +__always_inline __format_arg(2) +static inline const char * +fmtcheck_ok(const char *userfmt, const char *template) +{ + return userfmt; +} + +/* + * build_page -- + * Build a man page for display. + */ +static void +build_page(const char *fmt, char **pathp, struct manstate *mp) +{ + static int warned; + int olddir, fd, n; + size_t tmpdirlen; + char *p, *b; + char buf[MAXPATHLEN], cmd[MAXPATHLEN], tpath[MAXPATHLEN]; + const char *tmpdir; + + /* Let the user know this may take awhile. */ + if (!warned) { + warned = 1; + warnx("Formatting manual page..."); + } + + /* + * Historically man chdir'd to the root of the man tree. + * This was used in man pages that contained relative ".so" + * directives (including other man pages for command aliases etc.) + * It even went one step farther, by examining the first line + * of the man page and parsing the .so filename so it would + * make hard(?) links to the cat'ted man pages for space savings. + * (We don't do that here, but we could). + */ + + /* copy and find the end */ + for (b = buf, p = *pathp; (*b++ = *p++) != '\0';) + continue; + + /* + * skip the last two path components, page name and man[n] ... + * (e.g. buf will be "/usr/share/man" and p will be "man1/man.1") + * we also save a pointer to our current directory so that we + * can fchdir() back to it. this allows relative MANDIR paths + * to work with multiple man pages... e.g. consider: + * cd /usr/share && man -M ./man cat ls + * when no "cat1" subdir files are present. + */ + olddir = -1; + for (--b, --p, n = 2; b != buf; b--, p--) + if (*b == '/') + if (--n == 0) { + *b = '\0'; + olddir = open(".", O_RDONLY); + (void) chdir(buf); + p++; + break; + } + + + /* advance fmt past the suffix spec to the printf format string */ + for (; *fmt && isspace((unsigned char)*fmt); ++fmt) + continue; + + /* + * Get a temporary file and build a version of the file + * to display. Replace the old file name with the new one. + */ + if ((tmpdir = getenv("TMPDIR")) == NULL) + tmpdir = _PATH_TMP; + tmpdirlen = strlen(tmpdir); + (void)snprintf(tpath, sizeof (tpath), "%s%s%s", tmpdir, + (tmpdirlen > 0 && tmpdir[tmpdirlen-1] == '/') ? "" : "/", TMPFILE); + if ((fd = mkstemp(tpath)) == -1) { + warn("%s", tpath); + (void)cleanup(); + exit(EXIT_FAILURE); + } + (void)snprintf(buf, sizeof(buf), "%s > %s", fmt, tpath); + (void)snprintf(cmd, sizeof(cmd), fmtcheck_ok(buf, "%s"), p); + (void)system(cmd); + (void)close(fd); + if ((*pathp = strdup(tpath)) == NULL) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + + /* Link the built file into the remove-when-done list. */ + if (addentry(mp->intmp, *pathp, 0) < 0) { + warn("malloc"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + + /* restore old directory so relative manpaths still work */ + if (olddir != -1) { + fchdir(olddir); + close(olddir); + } +} + +/* + * how -- + * display how information + */ +static void +how(const char *fname) +{ + FILE *fp; + + int lcnt, print; + char buf[256]; + const char *p; + + if (!(fp = fopen(fname, "r"))) { + warn("%s", fname); + (void)cleanup(); + exit(EXIT_FAILURE); + } +#define S1 "SYNOPSIS" +#define S2 "S\bSY\bYN\bNO\bOP\bPS\bSI\bIS\bS" +#define D1 "DESCRIPTION" +#define D2 "D\bDE\bES\bSC\bCR\bRI\bIP\bPT\bTI\bIO\bON\bN" + for (lcnt = print = 0; fgets(buf, sizeof(buf), fp);) { + if (!strncmp(buf, S1, sizeof(S1) - 1) || + !strncmp(buf, S2, sizeof(S2) - 1)) { + print = 1; + continue; + } else if (!strncmp(buf, D1, sizeof(D1) - 1) || + !strncmp(buf, D2, sizeof(D2) - 1)) { + if (fp) + (void)fclose(fp); + return; + } + if (!print) + continue; + if (*buf == '\n') + ++lcnt; + else { + for(; lcnt; --lcnt) + (void)putchar('\n'); + for (p = buf; isspace((unsigned char)*p); ++p) + continue; + (void)fputs(p, stdout); + } + } + (void)fclose(fp); +} + +/* + * cat -- + * cat out the file + */ +static void +cat(const char *fname) +{ + int fd; + ssize_t n; + char buf[2048]; + + if ((fd = open(fname, O_RDONLY, 0)) < 0) { + warn("%s", fname); + (void)cleanup(); + exit(EXIT_FAILURE); + } + while ((n = read(fd, buf, sizeof(buf))) > 0) + if (write(STDOUT_FILENO, buf, (size_t)n) != n) { + warn("write"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + if (n == -1) { + warn("read"); + (void)cleanup(); + exit(EXIT_FAILURE); + } + (void)close(fd); +} + +/* + * check_pager -- + * check the user supplied page information + */ +static const char * +check_pager(const char *name) +{ + const char *p; + + /* + * if the user uses "more", we make it "more -s"; watch out for + * PAGER = "mypager /usr/ucb/more" + */ + for (p = name; *p && !isspace((unsigned char)*p); ++p) + continue; + for (; p > name && *p != '/'; --p); + if (p != name) + ++p; + + /* make sure it's "more", not "morex" */ + if (!strncmp(p, "more", 4) && (!p[4] || isspace((unsigned char)p[4]))){ + char *newname; + (void)asprintf(&newname, "%s %s", p, "-s"); + name = newname; + } + + return name; +} + +/* + * jump -- + * strip out flag argument and jump + */ +static void +jump(char **argv, const char *flag, const char *name) +{ + char **arg; + + argv[0] = __UNCONST(name); + for (arg = argv + 1; *arg; ++arg) + if (!strcmp(*arg, flag)) + break; + for (; *arg; ++arg) + arg[0] = arg[1]; + execvp(name, argv); + err(EXIT_FAILURE, "Cannot execute `%s'", name); +} + +/* + * onsig -- + * If signaled, delete the temporary files. + */ +static void +onsig(int signo) +{ + + (void)cleanup(); + + (void)raise_default_signal(signo); + + /* NOTREACHED */ + exit(EXIT_FAILURE); +} + +/* + * cleanup -- + * Clean up temporary files, show any error messages. + */ +static int +cleanup(void) +{ + TAG *intmpp, *missp; + ENTRY *ep; + int rval; + + rval = EXIT_SUCCESS; + /* + * note that _missing and _intmp were created by main(), so + * gettag() cannot return NULL here. + */ + missp = gettag("_missing", 0); /* missing man pages */ + intmpp = gettag("_intmp", 0); /* tmp files we need to unlink */ + + TAILQ_FOREACH(ep, &missp->entrylist, q) { + warnx("no entry for %s in the manual.", ep->s); + rval = EXIT_FAILURE; + } + + TAILQ_FOREACH(ep, &intmpp->entrylist, q) + (void)unlink(ep->s); + + return rval; +} + +static const char * +getclass(const char *machine) +{ + char buf[BUFSIZ]; + TAG *t; + snprintf(buf, sizeof(buf), "_%s", machine); + t = gettag(buf, 0); + return t != NULL && !TAILQ_EMPTY(&t->entrylist) ? + TAILQ_FIRST(&t->entrylist)->s : NULL; +} + +static void +addpath(struct manstate *m, const char *dir, size_t len, const char *sub) +{ + char buf[2 * MAXPATHLEN + 1]; + (void)snprintf(buf, sizeof(buf), "%s%s%s{/%s,%s%s%s}", + dir, (dir[len - 1] == '/') ? "" : "/", sub, m->machine, + m->machclass ? "/" : "", m->machclass ? m->machclass : "", + m->machclass ? "," : ""); + if (addentry(m->mymanpath, buf, 0) < 0) + errx(EXIT_FAILURE, "malloc failed"); +} + +/* + * usage -- + * print usage message and die + */ +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-acw|-h] [-C cfg] [-M path] " + "[-m path] [-S srch] [[-s] sect] name ...\n", getprogname()); + (void)fprintf(stderr, "Usage: %s [-C file] -f command ...\n", getprogname()); + (void)fprintf(stderr, + "Usage: %s [-C file] -k keyword ...\n", + getprogname()); + (void)fprintf(stderr, "Usage: %s -p\n", getprogname()); + exit(EXIT_FAILURE); +} + +/* + * printmanpath -- + * Prints a list of directories containing man pages. + */ +static void +printmanpath(struct manstate *m) +{ + ENTRY *epath; + char **ap; + glob_t pg; + struct stat sb; + TAG *path = m->mymanpath; + + /* the tail queue is empty if no _default tag is defined in * man.conf */ + if (TAILQ_EMPTY(&path->entrylist)) + errx(EXIT_FAILURE, "Empty manpath"); + + TAILQ_FOREACH(epath, &path->entrylist, q) { + if (glob(epath->s, GLOB_BRACE | GLOB_NOSORT, NULL, &pg) != 0) + err(EXIT_FAILURE, "glob failed"); + + if (pg.gl_matchc == 0) { + globfree(&pg); + continue; + } + + for (ap = pg.gl_pathv; *ap != NULL; ++ap) { + /* Skip cat page directories */ + if (strstr(*ap, "/cat") != NULL) + continue; + /* Skip non-directories. */ + if (stat(*ap, &sb) == 0 && S_ISDIR(sb.st_mode)) + printf("%s\n", *ap); + } + globfree(&pg); + } +} diff --git a/usr.bin/man/man.conf.5 b/usr.bin/man/man.conf.5 new file mode 100644 index 0000000..b89c74b --- /dev/null +++ b/usr.bin/man/man.conf.5 @@ -0,0 +1,286 @@ +.\" $NetBSD: man.conf.5,v 1.27 2016/06/16 15:11:43 abhinav Exp $ +.\" +.\" Copyright (c) 1989, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)man.conf.5 8.5 (Berkeley) 1/2/94 +.\" +.Dd June 16, 2016 +.Dt MAN.CONF 5 +.Os +.Sh NAME +.Nm man.conf +.Nd configuration file for manual pages +.Sh DESCRIPTION +The +.Nm +file contains the default configuration used by +.Xr man 1 , +.Xr apropos 1 , +.Xr whatis 1 , +.Xr catman 8 , +and +.Xr makemandb 8 +to find manual pages and information about manual pages (e.g. the +whatis database). +.Pp +Manual pages are located by searching an ordered set of directories +called the +.Dq man path +for a file that matches the name of the requested page. +Each directory in the search path usually has a set of subdirectories +in it (though this is not required). +When subdirectories are used, there are normally two subdirectories +for each section of the manual. +One subdirectory contains formatted copies of that section's manual +pages that can be directly displayed to a terminal, while the other +section subdirectory contains unformatted copies of the pages (see +.Xr nroff 1 +and +.Xr mdoc 7 ) . +Formatted manual pages are normally named with a trailing +.Dq \.0 +suffix. +.Pp +The +.Nm +file contains comment and configuration lines. +Comment lines start with the +.Dq # +character. +Blank lines are also treated as comment lines. +Configuration lines consist of a configuration keyword followed by a +configuration string. +There are two types of configuration keywords: control keywords and +section keywords. +Control keywords must start with the +.Dq _ +character. +The following control keywords are currently defined: +.Bl -tag -width XXmachineX +.It _build +Identifies the set of suffixes used for manual pages that must be +formatted for display and the command that should be used to format +them. +Manual file names, regardless of their format, are expected to end in a +.Dq \.* +pattern, i.e. a +.Dq \&\. +followed by some suffix. +The first field of a _build line contains a man page suffix specification. +The suffix specification may contain the normal shell globbing characters +(NOT including curly braces +.Pq Dq {} ) . +The rest of the _build line is a shell command line whose standard +output is a formatted manual page that can be directly displayed to +the user. +There should be exactly one occurrence of the string +.Dq %s +in the shell command line, and it will +be replaced by the name of the file which is being formatted. +.It _crunch +Used by +.Xr catman 8 +to determine how to crunch formatted pages +which originally were compressed man pages: The first field lists a suffix +which indicates what kind of compression were used to compress the man page. +The rest of the line must be a shell command line, used to compress the +formatted pages. +There should be exactly one occurrence of the string +.Dq %s +in the shell command line, and it will +be replaced by the name of the output file. +.It _default +Contains the system-wide default man path used to search for man pages. +.It _mandb +Defines the full pathname (not just a directory path) for a database to +be used +by the +.Xr apropos 1 +and +.Xr whatis 1 +commands. +The pathname may contain the normal shell globbing characters, +including curly braces +.Pq Dq {} ; +to escape a shell globbing character, +precede it with a backslash +.Pq Dq \e . +.It _subdir +Contains the list (in search order) of section subdirectories which will +be searched in any man path directory named with a trailing slash +.Pq Dq / +character. +This list is also used, even if there is no trailing slash character, +when a path is specified to the +.Xr man 1 +utility by the user, by the +.Ev MANPATH +environment variable, or by the +.Fl M +and +.Fl m +options. +.It _suffix +identifies the set of suffixes used for formatted man pages +(the +.Dq \.0 +suffix is normally used here). +Formatted man pages can be directly displayed to the user. +Each suffix may contain the normal shell globbing characters (NOT +including curly braces +.Pq Dq {} ) . +.It _version +Contains the version of the configuration file. +.It _ Ns Aq machine +Defines additional paths to be searched for the particular +.Dv machine +whose literal value is taken from +.Xr uname 1 +.Fl m . +For example on an +.Dv amd64 , +.Dv _amd64 +is used. +.El +.Pp +Section configuration lines in +.Nm +consist of a section keyword naming the section and a configuration +string that defines the directory or subdirectory path that the section's +manual pages are located in. +The path may contain the normal shell globbing characters, +including curly braces +.Pq Dq {} ; +to escape a shell globbing character, +precede it with a backslash +.Pq Dq \e . +Section keywords must not start with the +.Dq _ +character. +.Pp +A section path may contain either a list of absolute directories or +a list of or relative directories (but not both). +Relative directory paths are treated as a list of subdirectories that +are appended to the current man path directory being searched. +Section configuration lines with absolute directory paths (starting with +.Dq / ) +completely replace the current man search path directory with their +content. +.Pp +Section configuration lines with absolute directory paths ending +with a trailing slash character are expected to contain subdirectories +of manual pages, (see the keyword +.Dq _subdir +above). +The +.Dq _subdir +subdirectory list is not applied to absolute section directories +if there is no trailing slash. +.Pp +In addition to the above rules, the +.Xr man 1 +command also always checks in each directory that it searches for +a subdirectory with the same name as the current machine type. +If the machine-specific directory is found, it is also searched. +This allows the manual to contain machine-specific man pages. +Note that the machine subdirectory does not need to be specified +in the +.Nm +file. +.Pp +Multiple specifications for all types of +.Nm +configuration lines are +cumulative and the entries are used in the order listed in the file; +multiple entries may be listed per line, as well. +.Sh FILES +.Bl -tag -width /etc/man.conf -compact +.It Pa /etc/man.conf +Standard manual configuration file. +.El +.Sh EXAMPLES +Given the following +.Nm +file: +.Bd -literal -offset indent +_version BSD.2 +_subdir cat[123] +_suffix .0 +_build .[1-9] nroff -man %s +_build .tbl tbl %s | nroff -man +_i386 x86 +_default /usr/share/man/ +sect3 /usr/share/man/{old/,}cat3 +.Ed +.Pp +By default, the command +.Dq Li man mktemp +will search for +.Dq mktemp. Ns Aq any_digit +and +.Dq mktemp.tbl +in the directories +.Dq Pa /usr/share/man/cat1 , +.Dq Pa /usr/share/man/cat2 , +and +.Dq Pa /usr/share/man/cat3 . +If on a machine of type +.Dq vax , +the subdirectory +.Dq vax +in each +directory would be searched as well, before the directory was +searched. +.Pp +If +.Dq mktemp.tbl +was found first, the command +.Dq Li tbl Ao manual page Ac | nroff -man +would be run to build a man page for display to the user. +.Pp +The command +.Dq Li man sect3 mktemp +would search the directories +.Dq Pa /usr/share/man/old/cat3 +and +.Dq Pa /usr/share/man/cat3 , +in that order, for +the mktemp manual page. +If a subdirectory with the same name as the current machine type +existed in any of them, it would be searched as well, before each +of them were searched. +.Sh SEE ALSO +.Xr apropos 1 , +.Xr machine 1 , +.Xr man 1 , +.Xr whatis 1 , +.Xr whereis 1 , +.Xr fnmatch 3 , +.Xr glob 3 , +.Xr catman 8 , +.Xr makemandb 8 diff --git a/usr.bin/man/manconf.c b/usr.bin/man/manconf.c new file mode 100644 index 0000000..f12d031 --- /dev/null +++ b/usr.bin/man/manconf.c @@ -0,0 +1,272 @@ +/* $NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1995 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * manconf.c: provides interface for reading man.conf files + * + * note that this code is shared across all programs that read man.conf. + * (currently: apropos, catman, makewhatis, man, and whatis...) + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)config.c 8.8 (Berkeley) 1/31/95"; +#else +__RCSID("$NetBSD: manconf.c,v 1.8 2014/02/17 02:53:48 uwe Exp $"); +#endif +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "manconf.h" +#include "pathnames.h" + +TAILQ_HEAD(_head, _tag); +static struct _head head; /* 'head' -- top level data structure */ + +/* + * xstrdup: like strdup, but also returns length of string in lenp + */ +static char * +xstrdup(const char *str, size_t *lenp) +{ + size_t len; + char *copy; + + len = strlen(str) + 1; + copy = malloc(len); + if (!copy) + return NULL; + (void)memcpy(copy, str, len); + if (lenp) + *lenp = len - 1; /* subtract out the null */ + return copy; +} + +/* + * config -- + * + * Read the configuration file and build a doubly linked + * list off of "head" that looks like: + * + * tag1 <-> entry <-> entry <-> entry + * | + * tag2 <-> entry <-> entry <-> entry + * + * note: will err/errx out on error (fopen or malloc failure) + */ +void +config(const char *fname) +{ + TAG *tp; + FILE *cfp; + size_t len; + int lcnt; + char *p, *t, type; + + if (fname == NULL) + fname = _PATH_MANCONF; + if ((cfp = fopen(fname, "r")) == NULL) + err(EXIT_FAILURE, "%s", fname); + TAILQ_INIT(&head); + for (lcnt = 1; (p = fgetln(cfp, &len)) != NULL; ++lcnt) { + if (len == 1) /* Skip empty lines. */ + continue; + if (p[len - 1] != '\n') { /* Skip corrupted lines. */ + warnx("%s: line %d corrupted", fname, lcnt); + continue; + } + p[len - 1] = '\0'; /* Terminate the line. */ + + /* Skip leading space. */ + for (/*EMPTY*/; *p != '\0' && isspace((unsigned char)*p); ++p) + continue; + /* Skip empty/comment lines. */ + if (*p == '\0' || *p == '#') + continue; + /* Find first token. */ + for (t = p; *t && !isspace((unsigned char)*t); ++t) + continue; + if (*t == '\0') /* Need more than one token.*/ + continue; + *t = '\0'; + + tp = gettag(p, 1); + if (!tp) + errx(EXIT_FAILURE, "gettag: malloc failed"); + + /* + * Attach new records. Check to see if it is a + * section record or not. + */ + + if (*p == '_') { /* not a section record */ + /* + * Special cases: _build and _crunch take the + * rest of the line as a single entry. + */ + if (!strcmp(p, "_build") || !strcmp(p, "_crunch")) { + const char *u; + + /* + * The reason we're not just using + * strtok(3) for all of the parsing is + * so we don't get caught if a line + * has only a single token on it. + */ + while (*++t && isspace((unsigned char)*t)); +#ifndef HAVE_NBTOOL_CONFIG_H + /* pre-verify user-supplied command format */ + u = t; + while (*u && !isspace((unsigned char)*u)) + ++u; + while (*u && isspace((unsigned char)*u)) + ++u; + if (fmtcheck(u, "%s") != u) { + warnx("%s:%d: invalid %s command ignored", + fname, lcnt, p); + continue; + } +#endif /* !HAVE_NBTOOL_CONFIG_H */ + if (addentry(tp, t, 0) == -1) + errx(EXIT_FAILURE, + "addentry: malloc failed"); + } else { + for (++t; (p = strtok(t, " \t\n")) != NULL; + t = NULL) { + if (addentry(tp, p, 0) == -1) + errx(EXIT_FAILURE, + "addentry: malloc failed"); + } + } + + } else { /* section record */ + + /* + * section entries can either be all absolute + * paths or all relative paths, but not both. + */ + type = (char)((TAILQ_FIRST(&tp->entrylist) != NULL) ? + *(TAILQ_FIRST(&tp->entrylist)->s) : '\0'); + + for (++t; (p = strtok(t, " \t\n")) != NULL; t = NULL) { + + /* ensure an assigned type */ + if (type == 0) + type = *p; + + /* check for illegal mix */ + if (*p != type) { + warnx("section %s: %s: invalid entry, does not match previous types", + tp->s, p); + warnx("man.conf cannot mix absolute and relative paths in an entry"); + continue; + } + if (addentry(tp, p, 0) == -1) + errx(EXIT_FAILURE, + "addentry: malloc failed"); + } + } + } + (void)fclose(cfp); +} + +/* + * gettag -- + * if (!create) return tag for given name if it exists, or NULL otherwise + * + * if (create) return tag for given name if it exists, try and create + * a new tag if it does not exist. return NULL if unable to create new + * tag. + */ +TAG * +gettag(const char *name, int create) +{ + TAG *tp; + + TAILQ_FOREACH(tp, &head, q) + if (!strcmp(name, tp->s)) + return tp; + if (!create) + return NULL; + + /* try and add it in */ + tp = malloc(sizeof(*tp)); + if (tp) + tp->s = xstrdup(name, &tp->len); + if (!tp || !tp->s) { + if (tp) + free(tp); + return NULL; + } + TAILQ_INIT(&tp->entrylist); + TAILQ_INSERT_TAIL(&head, tp, q); + return tp; +} + +/* + * addentry -- + * add an entry to a list. + * returns -1 if malloc failed, otherwise 0. + */ +int +addentry(TAG *tp, const char *newent, int ishead) +{ + ENTRY *ep; + + ep = malloc(sizeof(*ep)); + if (ep) + ep->s = xstrdup(newent, &ep->len); + if (!ep || !ep->s) { + if (ep) + free(ep); + return -1; + } + if (ishead) + TAILQ_INSERT_HEAD(&tp->entrylist, ep, q); + else + TAILQ_INSERT_TAIL(&tp->entrylist, ep, q); + + return 0; +} diff --git a/usr.bin/man/manconf.h b/usr.bin/man/manconf.h new file mode 100644 index 0000000..ab87728 --- /dev/null +++ b/usr.bin/man/manconf.h @@ -0,0 +1,60 @@ +/* $NetBSD: manconf.h,v 1.3 2006/04/10 14:39:06 chuck Exp $ */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)config.h 8.4 (Berkeley) 12/18/93 + */ + +/* + * manconf.h: common data structures and APIs shared across all programs + * that access man.conf (currently: apropos, catman, makewhatis, man, and + * whatis). + */ + +/* TAG: top-level structure (one per section/reserved word) */ +typedef struct _tag { + TAILQ_ENTRY(_tag) q; /* Queue of tags */ + + TAILQ_HEAD(tqh, _entry) entrylist; /* Queue of entries */ + char *s; /* Associated string */ + size_t len; /* Length of 's' */ +} TAG; + +/* ENTRY: each TAG has one or more ENTRY strings linked off of it */ +typedef struct _entry { + TAILQ_ENTRY(_entry) q; /* Queue of entries */ + + char *s; /* Associated string */ + size_t len; /* Length of 's' */ +} ENTRY; + +int addentry(TAG *, const char *, int); +void config(const char *); +TAG *gettag(const char *, int); diff --git a/usr.bin/man/pathnames.h b/usr.bin/man/pathnames.h new file mode 100644 index 0000000..15aa211 --- /dev/null +++ b/usr.bin/man/pathnames.h @@ -0,0 +1,39 @@ +/* $NetBSD: pathnames.h,v 1.7 2016/05/21 20:54:34 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.3 (Berkeley) 1/2/94 + */ + +#include + +#define _PATH_MANCONF "/etc/man.conf" +#define _PATH_PAGER "/usr/bin/more -s" +#define _PATH_WHATIS "whatis.db" +#define TMPFILE "man.XXXXXX" diff --git a/usr.bin/mesg/mesg.1 b/usr.bin/mesg/mesg.1 new file mode 100644 index 0000000..e5d9100 --- /dev/null +++ b/usr.bin/mesg/mesg.1 @@ -0,0 +1,89 @@ +.\" $NetBSD: mesg.1,v 1.10 2017/07/03 21:34:20 wiz Exp $ +.\" +.\" Copyright (c) 1987, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)mesg.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt MESG 1 +.Os +.Sh NAME +.Nm mesg +.Nd display (do not display) messages from other users +.Sh SYNOPSIS +.Nm +.Op Cm n | Cm y +.Sh DESCRIPTION +The +.Nm +utility is invoked by a user to control write access others +have to the terminal device associated with the standard error +output. +Write access is allowed by default, and programs such as +.Xr talk 1 +and +.Xr write 1 +may display messages on the terminal. +.Pp +Options available: +.Bl -tag -width flag +.It Cm n +Disallows messages. +.It Cm y +Permits messages to be displayed. +.El +.Pp +If no arguments are given, +.Nm +displays the present message status to the standard error output. +.Pp +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width flag -compact -offset indent +.It Li "\ 0" +Messages are allowed. +.It Li "\ 1" +Messages are not allowed. +.It Li ">1" +An error has occurred. +.El +.Sh FILES +.Bl -tag -width /dev/[pt]ty[pq]? -compact +.It Pa /dev/[pt]ty[pq]? +.El +.Sh SEE ALSO +.Xr biff 1 , +.Xr talk 1 , +.Xr write 1 +.Sh HISTORY +A +.Nm +command appeared in +.At v6 . diff --git a/usr.bin/mesg/mesg.c b/usr.bin/mesg/mesg.c new file mode 100644 index 0000000..cc54a75 --- /dev/null +++ b/usr.bin/mesg/mesg.c @@ -0,0 +1,108 @@ +/* $NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $ */ + +/* + * Copyright (c) 1987, 1993 + * The Regents of the University of California. All rights reserved. + * (c) UNIX System Laboratories, Inc. + * All or some portions of this file are derived from material licensed + * to the University of California by American Telephone and Telegraph + * Co. or Unix System Laboratories, Inc. and are reproduced herein with + * the permission of UNIX System Laboratories, Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1987, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mesg.c 8.2 (Berkeley) 1/21/94"; +#endif +__RCSID("$NetBSD: mesg.c,v 1.8 2008/07/21 14:19:24 lukem Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +int +main(int argc, char *argv[]) +{ + struct stat sb; + char *tty; + int ch; + + setprogname(*argv); + + while ((ch = getopt(argc, argv, "")) != -1) + switch (ch) { + case '?': + default: + goto usage; + } + argc -= optind; + argv += optind; + + if ((tty = ttyname(STDIN_FILENO)) == NULL && + (tty = ttyname(STDOUT_FILENO)) == NULL && + (tty = ttyname(STDERR_FILENO)) == NULL) + err(2, "ttyname"); + if (stat(tty, &sb) == -1) + err(2, "%s", tty); + + if (*argv == NULL) { + if (sb.st_mode & S_IWGRP) { + (void)fprintf(stderr, "is y\n"); + return 0; + } + (void)fprintf(stderr, "is n\n"); + return 1; + } + + switch (*argv[0]) { + case 'y': + if (chmod(tty, sb.st_mode | S_IWGRP) == -1) + err(2, "%s", tty); + return 0; + case 'n': + if (chmod(tty, sb.st_mode & ~S_IWGRP) == -1) + err(2, "%s", tty); + return 1; + } + +usage: (void)fprintf(stderr, "Usage: %s [y | n]\n", getprogname()); + return 2; +} diff --git a/usr.bin/mkfifo/mkfifo.1 b/usr.bin/mkfifo/mkfifo.1 new file mode 100644 index 0000000..a178dc2 --- /dev/null +++ b/usr.bin/mkfifo/mkfifo.1 @@ -0,0 +1,86 @@ +.\" $NetBSD: mkfifo.1,v 1.13 2017/07/04 07:03:32 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)mkfifo.1 8.2 (Berkeley) 1/5/94 +.\" +.Dd January 5, 1994 +.Dt MKFIFO 1 +.Os +.Sh NAME +.Nm mkfifo +.Nd make fifos +.Sh SYNOPSIS +.Nm +.Op Fl m Ar mode +.Ar fifo_name ... +.Sh DESCRIPTION +.Nm +creates the fifos requested, in the order specified, +using mode +.Li \&0666 +modified by the current +.Xr umask 2 . +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl m +Set the file permission bits of newly-created fifos to +.Ar mode . +The mode is specified as in +.Xr chmod 1 . +In symbolic mode strings, the +.Dq + +and +.Dq - +operators are interpreted relative to an assumed initial mode of +.Dq a=rw +.El +.Pp +.Nm +requires write permission in the parent directory. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr mkdir 1 , +.Xr rm 1 , +.Xr mkfifo 2 , +.Xr mknod 8 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2-92 +compliant. +.Sh HISTORY +.Nm +command appeared in +.Bx 4.4 . diff --git a/usr.bin/mkfifo/mkfifo.c b/usr.bin/mkfifo/mkfifo.c new file mode 100644 index 0000000..9d49ec5 --- /dev/null +++ b/usr.bin/mkfifo/mkfifo.c @@ -0,0 +1,107 @@ +/* $NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $ */ + +/* + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1990, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mkfifo.c 8.2 (Berkeley) 1/5/94"; +#endif +__RCSID("$NetBSD: mkfifo.c,v 1.13 2011/09/04 20:30:34 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + int ch, exitval; + void *set; + mode_t mode; + + setlocale (LC_ALL, ""); + + /* The default mode is the value of the bitwise inclusive or of + S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP, S_IROTH, and S_IWOTH + modified by the file creation mask */ + mode = 0666 & ~umask(0); + + while ((ch = getopt(argc, argv, "m:")) != -1) + switch(ch) { + case 'm': + if (!(set = setmode(optarg))) { + err(1, "Cannot set file mode `%s'", optarg); + /* NOTREACHED */ + } + /* In symbolic mode strings, the + and - operators are + interpreted relative to an assumed initial mode of + a=rw. */ + mode = getmode(set, 0666); + free(set); + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + if (argv[0] == NULL) + usage(); + + for (exitval = 0; *argv; ++argv) { + if (mkfifo(*argv, mode) < 0) { + warn("%s", *argv); + exitval = 1; + } + } + exit(exitval); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: mkfifo [-m mode] fifoname ...\n"); + exit(1); +} diff --git a/usr.bin/mkstr/mkstr.1 b/usr.bin/mkstr/mkstr.1 new file mode 100644 index 0000000..5fd812d --- /dev/null +++ b/usr.bin/mkstr/mkstr.1 @@ -0,0 +1,131 @@ +.\" $NetBSD: mkstr.1,v 1.13 2017/07/03 21:34:20 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)mkstr.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt MKSTR 1 +.Os +.Sh NAME +.Nm mkstr +.Nd create an error message file by massaging C source +.Sh SYNOPSIS +.Nm +.Op Fl +.Ar messagefile +.Ar prefix file ... +.Sh DESCRIPTION +.Nm +creates files containing error messages extracted from C source, +and restructures the same C source, to use the created error message +file. +The intent of +.Nm +was to reduce the size of large programs and +reduce swapping (see +.Sx BUGS +section below). +.Pp +.Nm +processes each of the specified +.Ar files , +placing a restructured version of the input in a file whose name +consists of the specified +.Ar prefix +and the original name. +A typical usage of +.Nm +is +.Bd -literal -offset indent +mkstr pistrings xx *.c +.Ed +.Pp +This command causes all the error messages from the C source +files in the current directory to be placed in the file +.Ar pistrings +and restructured copies of the sources to be placed in +files whose names are prefixed with +.Ar \&xx . +.Pp +Options: +.Bl -tag -width indent +.It Fl +Error messages are placed at the end of the specified +message file for recompiling part of a large +.Nm +ed +program. +.El +.Pp +.Nm +finds error messages in the source by +searching for the string +.Li \&`error("' +in the input stream. +Each time it occurs, the C string starting at the +.Sq \&"\& +is stored +in the message file followed by a null character and a new-line character; +The new source is restructured with +.Xr lseek 2 +pointers into the error message file for retrieval. +.Bd -literal -offset indent +char efilname = "/usr/lib/pi_strings"; +int efil = -1; + +error(a1, a2, a3, a4) +\&{ + char buf[256]; + + if (efil < 0) { + efil = open(efilname, 0); + if (efil < 0) { +oops: + perror(efilname); + exit 1 ; + } + } + if (lseek(efil, a1, 0) < 0 || read(efil, buf, 256) \*[Le] 0) + goto oops; + printf(buf, a2, a3, a4); +} +.Ed +.Sh SEE ALSO +.Xr xstr 1 , +.Xr lseek 2 +.Sh HISTORY +.Nm +appeared in +.Bx 1 . +.Sh BUGS +.Nm +was intended for the limited architecture of the PDP-11 family. +Very few programs actually use it. +It is not an efficient method, the error messages +should be stored in the program text. diff --git a/usr.bin/mkstr/mkstr.c b/usr.bin/mkstr/mkstr.c new file mode 100644 index 0000000..0bfe6ca --- /dev/null +++ b/usr.bin/mkstr/mkstr.c @@ -0,0 +1,320 @@ +/* $NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $ */ + +/* + * Copyright (c) 1980, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)mkstr.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: mkstr.c,v 1.17 2016/09/05 00:40:29 sevan Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#define ungetchar(c) ungetc(c, stdin) + +/* + * mkstr - create a string error message file by massaging C source + * + * Bill Joy UCB August 1977 + * + * Modified March 1978 to hash old messages to be able to recompile + * without addding messages to the message file (usually) + * + * Based on an earlier program conceived by Bill Joy and Chuck Haley + * + * Program to create a string error message file + * from a group of C programs. Arguments are the name + * of the file where the strings are to be placed, the + * prefix of the new files where the processed source text + * is to be placed, and the files to be processed. + * + * The program looks for 'error("' in the source stream. + * Whenever it finds this, the following characters from the '"' + * to a '"' are replaced by 'seekpt' where seekpt is a + * pointer into the error message file. + * If the '(' is not immediately followed by a '"' no change occurs. + * + * The optional '-' causes strings to be added at the end of the + * existing error message file for recompilation of single routines. + */ + + +FILE *mesgread, *mesgwrite; +char *progname; +const char usagestr[] = "usage: %s [ - ] mesgfile prefix file ...\n"; +char name[100], *np; + +void process(void); +int match(const char *); +int octdigit(char); +void inithash(void); +long hashit(const char *, char, long); +void copystr(void); +int fgetNUL(char *, int, FILE *); + +int +main(int argc, char *argv[]) +{ + char addon = 0; + + argc--, progname = *argv++; + if (argc > 1 && argv[0][0] == '-') + addon++, argc--, argv++; + if (argc < 3) + fprintf(stderr, usagestr, progname), exit(1); + mesgwrite = fopen(argv[0], addon ? "a" : "w"); + if (mesgwrite == NULL) + perror(argv[0]), exit(1); + mesgread = fopen(argv[0], "r"); + if (mesgread == NULL) + perror(argv[0]), exit(1); + inithash(); + argc--, argv++; + strlcpy(name, argv[0], sizeof(name)); + np = name + strlen(name); + argc--, argv++; + do { + strlcpy(np, argv[0], sizeof(name) - (np - name)); + if (freopen(name, "w", stdout) == NULL) + perror(name), exit(1); + if (freopen(argv[0], "r", stdin) == NULL) + perror(argv[0]), exit(1); + process(); + argc--, argv++; + } while (argc > 0); + exit(0); +} + +void +process(void) +{ + int c; + + for (;;) { + c = getchar(); + if (c == EOF) + return; + if (c != 'e') { + putchar(c); + continue; + } + if (match("error(")) { + printf("error("); + c = getchar(); + if (c != '"') + putchar(c); + else + copystr(); + } + } +} + +int +match(const char *ocp) +{ + const char *cp; + int c; + + for (cp = ocp + 1; *cp; cp++) { + c = getchar(); + if (c != *cp) { + while (ocp < cp) + putchar(*ocp++); + ungetchar(c); + return (0); + } + } + return (1); +} + +void +copystr(void) +{ + int c, ch; + char buf[512]; + char *cp = buf; + + for (;;) { + c = getchar(); + if (c == EOF) + break; + switch (c) { + + case '"': + *cp++ = 0; + goto out; + case '\\': + c = getchar(); + switch (c) { + + case 'b': + c = '\b'; + break; + case 't': + c = '\t'; + break; + case 'r': + c = '\r'; + break; + case 'n': + c = '\n'; + break; + case '\n': + continue; + case 'f': + c = '\f'; + break; + case '0': + c = 0; + break; + case '\\': + break; + default: + if (!octdigit(c)) + break; + c -= '0'; + ch = getchar(); + if (!octdigit(ch)) + break; + c <<= 7, c += ch - '0'; + ch = getchar(); + if (!octdigit(ch)) + break; + c <<= 3, c+= ch - '0', ch = -1; + break; + } + } + *cp++ = c; + } +out: + *cp = 0; + printf("%ld", hashit(buf, 1, 0)); +} + +int +octdigit(char c) +{ + + return (c >= '0' && c <= '7'); +} + +void +inithash(void) +{ + char buf[512]; + long mesgpt = 0; + + rewind(mesgread); + while (fgetNUL(buf, sizeof buf, mesgread) != 0) { + hashit(buf, 0, mesgpt); + mesgpt += strlen(buf) + 2; + } +} + +#define NBUCKETS 511 + +struct hash { + long hval; + long hpt; + struct hash *hnext; +} *bucket[NBUCKETS]; + +long +hashit(const char *str, char really, long fakept) +{ + int i; + struct hash *hp; + char buf[512]; + long hashval = 0; + const char *cp; + +#ifdef __GNUC__ + hp = NULL; /* XXX gcc */ +#endif + if (really) + fflush(mesgwrite); + for (cp = str; *cp;) + hashval = (hashval << 1) + *cp++; + i = hashval % NBUCKETS; + if (i < 0) + i += NBUCKETS; + if (really != 0) + for (hp = bucket[i]; hp != 0; hp = hp->hnext) + if (hp->hval == hashval) { + fseek(mesgread, hp->hpt, 0); + fgetNUL(buf, sizeof buf, mesgread); +/* + fprintf(stderr, "Got (from %ld) %s\n", hp->hpt, buf); +*/ + if (strcmp(buf, str) == 0) + break; + } + if (!really || hp == 0) { + hp = (struct hash *) calloc(1, sizeof *hp); + hp->hnext = bucket[i]; + hp->hval = hashval; + hp->hpt = really ? ftell(mesgwrite) : fakept; + if (really) { + fwrite(str, sizeof (char), strlen(str) + 1, mesgwrite); + fwrite("\n", sizeof (char), 1, mesgwrite); + } + bucket[i] = hp; + } +/* + fprintf(stderr, "%s hashed to %ld at %ld\n", str, hp->hval, hp->hpt); +*/ + return (hp->hpt); +} + +#include +#include + +int +fgetNUL(char *obuf, int rmdr, FILE *file) +{ + int c; + char *buf = obuf; + + while (--rmdr > 0 && (c = getc(file)) != 0 && c != EOF) + *buf++ = c; + *buf++ = 0; + getc(file); + return ((feof(file) || ferror(file)) ? 0 : 1); +} diff --git a/usr.bin/newgrp/grutil.c b/usr.bin/newgrp/grutil.c new file mode 100644 index 0000000..ee81590 --- /dev/null +++ b/usr.bin/newgrp/grutil.c @@ -0,0 +1,338 @@ +/* $NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $ */ + +/*- + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Brian Ginsbach. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#include +__RCSID("$NetBSD: grutil.c,v 1.4 2014/06/23 06:57:31 shm Exp $"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LOGIN_CAP +#include +#endif + +#include "grutil.h" + +typedef enum { + ADDGRP_NOERROR = 0, /* must be zero */ + ADDGRP_EMALLOC = 1, + ADDGRP_EGETGROUPS = 2, + ADDGRP_ESETGROUPS = 3 +} addgrp_ret_t; + +static void +free_groups(void *groups) +{ + int oerrno; + + oerrno = errno; + free(groups); + errno = oerrno; +} + +static addgrp_ret_t +alloc_groups(int *ngroups, gid_t **groups, int *ngroupsmax) +{ + *ngroupsmax = (int)sysconf(_SC_NGROUPS_MAX); + if (*ngroupsmax < 0) + *ngroupsmax = NGROUPS_MAX; + + *groups = malloc(*ngroupsmax * sizeof(**groups)); + if (*groups == NULL) + return ADDGRP_EMALLOC; + + *ngroups = getgroups(*ngroupsmax, *groups); + if (*ngroups == -1) { + free_groups(*groups); + return ADDGRP_ESETGROUPS; + } + return ADDGRP_NOERROR; +} + +static addgrp_ret_t +addgid(gid_t *groups, int ngroups, int ngroupsmax, gid_t gid, int makespace) +{ + int i; + + /* search for gid in supplemental group list */ + for (i = 0; i < ngroups && groups[i] != gid; i++) + continue; + + /* add the gid to the supplemental group list */ + if (i == ngroups) { + if (ngroups < ngroupsmax) + groups[ngroups++] = gid; + else { /* + * setgroups(2) will fail with errno = EINVAL + * if ngroups > nmaxgroups. If makespace is + * set, replace the last group with the new + * one. Otherwise, fail the way setgroups(2) + * would if we passed the larger groups array. + */ + if (makespace) { + /* + * Find a slot that doesn't contain + * the primary group. + */ + struct passwd *pwd; + gid_t pgid; + pwd = getpwuid(getuid()); + if (pwd == NULL) + goto error; + pgid = pwd->pw_gid; + for (i = ngroupsmax - 1; i >= 0; i--) + if (groups[i] != pgid) + break; + if (i < 0) + goto error; + groups[i] = gid; + } + else { + error: + errno = EINVAL; + return ADDGRP_ESETGROUPS; + } + } + if (setgroups(ngroups, groups) < 0) + return ADDGRP_ESETGROUPS; + } + return ADDGRP_NOERROR; +} + +static addgrp_ret_t +addgrp(gid_t newgid, int makespace) +{ + int ngroups, ngroupsmax; + addgrp_ret_t rval; + gid_t *groups; + gid_t oldgid; + + oldgid = getgid(); + if (oldgid == newgid) /* nothing to do */ + return ADDGRP_NOERROR; + + rval = alloc_groups(&ngroups, &groups, &ngroupsmax); + if (rval != ADDGRP_NOERROR) + return rval; + + /* + * BSD based systems normally have the egid in the supplemental + * group list. + */ +#if (defined(BSD) && BSD >= 199306) + /* + * According to POSIX/XPG6: + * On system where the egid is normally in the supplemental group list + * (or whenever the old egid actually is in the supplemental group + * list): + * o If the new egid is in the supplemental group list, + * just change the egid. + * o If the new egid is not in the supplemental group list, + * add the new egid to the list if there is room. + */ + + rval = addgid(groups, ngroups, ngroupsmax, newgid, makespace); +#else + /* + * According to POSIX/XPG6: + * On systems where the egid is not normally in the supplemental group + * list (or whenever the old egid is not in the supplemental group + * list): + * o If the new egid is in the supplemental group list, delete + * it from the list. + * o If the old egid is not in the supplemental group list, + * add the old egid to the list if there is room. + */ + { + int i; + + /* search for new egid in supplemental group list */ + for (i = 0; i < ngroups && groups[i] != newgid; i++) + continue; + + /* remove new egid from supplemental group list */ + if (i != ngroups) + for (--ngroups; i < ngroups; i++) + groups[i] = groups[i + 1]; + + rval = addgid(groups, ngroups, ngroupsmax, oldgid, makespace); + } +#endif + free_groups(groups); + return rval; +} + +/* + * If newgrp fails, it returns (gid_t)-1 and the errno variable is + * set to: + * [EINVAL] Unknown group. + * [EPERM] Bad password. + */ +static gid_t +newgrp(const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) +{ + struct group *grp; + char **ap; + char *p; + gid_t *groups; + int ngroups, ngroupsmax; + + if (gname == NULL) + return pwd->pw_gid; + + grp = getgrnam(gname); + +#ifdef GRUTIL_ACCEPT_GROUP_NUMBERS + if (grp == NULL) { + gid_t gid; + if (*gname != '-') { + gid = (gid_t)strtol(gname, &p, 10); + if (*p == '\0') + grp = getgrgid(gid); + } + } +#endif + if (grp == NULL) { + errno = EINVAL; + return (gid_t)-1; + } + + if (ruid == 0 || pwd->pw_gid == grp->gr_gid) + return grp->gr_gid; + + if (alloc_groups(&ngroups, &groups, &ngroupsmax) == ADDGRP_NOERROR) { + int i; + for (i = 0; i < ngroups; i++) + if (groups[i] == grp->gr_gid) { + free_groups(groups); + return grp->gr_gid; + } + free_groups(groups); + } + + /* + * Check the group membership list in case the groups[] array + * was maxed out or the user has been added to it since login. + */ + for (ap = grp->gr_mem; *ap != NULL; ap++) + if (strcmp(*ap, pwd->pw_name) == 0) + return grp->gr_gid; + + if (*grp->gr_passwd != '\0') { + p = getpass(prompt); + if (strcmp(grp->gr_passwd, crypt(p, grp->gr_passwd)) == 0) { + (void)memset(p, '\0', _PASSWORD_LEN); + return grp->gr_gid; + } + (void)memset(p, '\0', _PASSWORD_LEN); + } + + errno = EPERM; + return (gid_t)-1; +} + +#ifdef GRUTIL_SETGROUPS_MAKESPACE +# define ADDGRP_MAKESPACE 1 +#else +# define ADDGRP_MAKESPACE 0 +#endif + +#ifdef GRUTIL_ALLOW_GROUP_ERRORS +# define maybe_exit(e) +#else +# define maybe_exit(e) exit(e); +#endif + +void +addgroup( +#ifdef LOGIN_CAP + login_cap_t *lc, +#endif + const char *gname, struct passwd *pwd, uid_t ruid, const char *prompt) +{ + pwd->pw_gid = newgrp(gname, pwd, ruid, prompt); + if (pwd->pw_gid == (gid_t)-1) { + switch (errno) { + case EINVAL: + warnx("Unknown group `%s'", gname); + maybe_exit(EXIT_FAILURE); + break; + case EPERM: /* password failure */ + warnx("Sorry"); + maybe_exit(EXIT_FAILURE); + break; + default: /* XXX - should never happen */ + err(EXIT_FAILURE, "unknown error"); + break; + } + pwd->pw_gid = getgid(); + } + + switch (addgrp(pwd->pw_gid, ADDGRP_MAKESPACE)) { + case ADDGRP_NOERROR: + break; + case ADDGRP_EMALLOC: + err(EXIT_FAILURE, "malloc"); + break; + case ADDGRP_EGETGROUPS: + err(EXIT_FAILURE, "getgroups"); + break; + case ADDGRP_ESETGROUPS: + switch(errno) { + case EINVAL: + warnx("setgroups: ngroups > ngroupsmax"); + maybe_exit(EXIT_FAILURE); + break; + case EPERM: + case EFAULT: + default: + warn("setgroups"); + maybe_exit(EXIT_FAILURE); + break; + } + break; + } + +#ifdef LOGIN_CAP + if (setusercontext(lc, pwd, pwd->pw_uid, LOGIN_SETGID) == -1) + err(EXIT_FAILURE, "setting user context"); +#else + if (setgid(pwd->pw_gid) == -1) + err(EXIT_FAILURE, "setgid"); +#endif +} diff --git a/usr.bin/newgrp/grutil.h b/usr.bin/newgrp/grutil.h new file mode 100644 index 0000000..957f798 --- /dev/null +++ b/usr.bin/newgrp/grutil.h @@ -0,0 +1,40 @@ +/* $NetBSD: grutil.h,v 1.2 2008/04/28 20:24:14 martin Exp $ */ + +/*- + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Brian Ginsbach. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef _GRUTIL_H_ +#define _GRUTIL_H_ + +void addgroup( +#ifdef LOGIN_CAP + login_cap_t *, +#endif + const char *, struct passwd *, uid_t, const char *); + +#endif /* _GRUTIL_H_ */ diff --git a/usr.bin/newgrp/newgrp.1 b/usr.bin/newgrp/newgrp.1 new file mode 100644 index 0000000..4bd4785 --- /dev/null +++ b/usr.bin/newgrp/newgrp.1 @@ -0,0 +1,121 @@ +.\" $NetBSD: newgrp.1,v 1.5 2017/07/03 21:34:20 wiz Exp $ +.\" +.\" Copyright (c) 2007, The NetBSD Foundation. +.\" All Rights Reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Brian Ginsbach. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 6, 2007 +.Dt NEWGRP 1 +.Os +.Sh NAME +.Nm newgrp +.Nd change to a new primary group +.Sh SYNOPSIS +.Nm +.Op Fl l +.Op Ar group +.Sh DESCRIPTION +The +.Nm +command changes a user to a new primary group +.Pq real and effective group ID +by starting a new shell. +The user remains logged in and the current directory +and file creation mask remain unchanged. +The user is always given a new shell even if +the primary group change fails. +.Pp +The +.Nm +command accepts the following options: +.Bl -tag -width indent +.It Fl l +The environment is changed to what would be expected if the user +actually logged in again. +This simulates a full login. +.El +.Pp +The +.Ar group +is a group name or non-negative numeric group ID from the group database. +The real and effective group IDs are set to +.Ar group +or the group ID associated with the group name. +.Pp +If +.Ar group +is not specified, +.Nm +restores the user's real and effective group IDs to the user's +primary group specified in the password database. +The user's supplementary group IDs are restored to the set specified +for the user in the group database. +.Pp +If the user is not a member of the specified group, and the group +requires a password, the user will be prompted for the group password. +.Sh FILES +.Bl -tag -width /etc/master.passwd -compact +.It Pa /etc/group +The group database +.It Pa /etc/master.passwd +The user database +.It Pa /etc/passwd +A Version 7 format password file +.El +.Sh EXIT STATUS +If a new shell is started the exit status is the exit status of the shell. +Otherwise the exit status will be >0. +.Sh SEE ALSO +.Xr csh 1 , +.Xr groups 1 , +.Xr login 1 , +.Xr sh 1 , +.Xr su 1 , +.Xr umask 2 , +.Xr group 5 , +.Xr passwd 5 , +.Xr environ 7 +.Sh STANDARDS +The +.Nm +command conforms to +.St -p1003.1-2001 . +.Sh HISTORY +A +.Nm +command appeared in +.At v6 . +A +.Nm +command appeared in +.Nx 5.0 . +.Sh BUGS +There is no convenient way to enter a password into +.Pa /etc/group . +The use of group passwords is strongly discouraged +since they are inherently insecure. +It is not possible to stop users from obtaining the encrypted +password from the group database. diff --git a/usr.bin/newgrp/newgrp.c b/usr.bin/newgrp/newgrp.c new file mode 100644 index 0000000..61309c3 --- /dev/null +++ b/usr.bin/newgrp/newgrp.c @@ -0,0 +1,195 @@ +/*- + * Copyright (c) 2007 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Brian Ginsbach. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ifndef lint +__RCSID("$NetBSD: newgrp.c,v 1.7 2011/09/16 15:39:27 joerg Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LOGIN_CAP +#include +#endif + +#include "grutil.h" + +__dead static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-l] [group]\n", getprogname()); + exit(EXIT_FAILURE); +} + +int +main(int argc, char *argv[]) +{ + extern char **environ; + struct passwd *pwd; + int c, lflag; + char *shell, sbuf[MAXPATHLEN + 2]; + uid_t uid; +#ifdef LOGIN_CAP + login_cap_t *lc; + u_int flags = LOGIN_SETUSER; +#endif + + uid = getuid(); + pwd = getpwuid(uid); + if (pwd == NULL) + errx(EXIT_FAILURE, "who are you?"); + +#ifdef LOGIN_CAP + if ((lc = login_getclass(pwd->pw_class)) == NULL) + errx(EXIT_FAILURE, "%s: unknown login class", pwd->pw_class); +#endif + + (void)setprogname(argv[0]); + lflag = 0; + while ((c = getopt(argc, argv, "-l")) != -1) { + switch (c) { + case '-': + case 'l': + if (lflag) + usage(); + lflag = 1; + break; + default: + usage(); + break; + } + } + + argc -= optind; + argv += optind; + + if (argc > 0) { +#if 0 + pwd->pw_gid = newgrp(*argv, pwd); + addgrp(pwd->pw_gid); + if (setgid(pwd->pw_gid) < 0) + err(1, "setgid"); +#endif +#ifdef LOGIN_CAP + addgroup(lc, *argv, pwd, getuid(), "Password:"); +#else + addgroup(*argv, pwd, getuid(), "Password:"); +#endif + } else { +#ifdef LOGIN_CAP + flags |= LOGIN_SETGROUP; +#else + if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) + err(EXIT_FAILURE, "initgroups"); + if (setgid(pwd->pw_gid) == -1) + err(EXIT_FAILURE, "setgid"); +#endif + } + +#ifdef LOGIN_CAP + if (setusercontext(lc, pwd, uid, flags) == -1) + err(EXIT_FAILURE, "setusercontext"); + if (!lflag) + login_close(lc); +#else + if (setuid(pwd->pw_uid) == -1) + err(EXIT_FAILURE, "setuid"); +#endif + + if (*pwd->pw_shell == '\0') { +#ifdef TRUST_ENV_SHELL + shell = getenv("SHELL"); + if (shell != NULL) + pwd->pw_shell = shell; + else +#endif + pwd->pw_shell = __UNCONST(_PATH_BSHELL); + } + + shell = pwd->pw_shell; + + if (lflag) { + char *term; +#ifdef KERBEROS + char *krbtkfile; +#endif + + if (chdir(pwd->pw_dir) == -1) + warn("%s", pwd->pw_dir); + + term = getenv("TERM"); +#ifdef KERBEROS + krbtkfile = getenv("KRBTKFILE"); +#endif + + /* create an empty environment */ + if ((environ = malloc(sizeof(char *))) == NULL) + err(EXIT_FAILURE, NULL); + environ[0] = NULL; +#ifdef LOGIN_CAP + if (setusercontext(lc, pwd, uid, LOGIN_SETENV | LOGIN_SETPATH) == -1) + err(EXIT_FAILURE, "setusercontext"); + login_close(lc); +#else + (void)setenv("PATH", _PATH_DEFPATH, 1); +#endif + if (term != NULL) + (void)setenv("TERM", term, 1); +#ifdef KERBEROS + if (krbtkfile != NULL) + (void)setenv("KRBTKFILE", krbtkfile, 1); +#endif + + (void)setenv("LOGNAME", pwd->pw_name, 1); + (void)setenv("USER", pwd->pw_name, 1); + (void)setenv("HOME", pwd->pw_dir, 1); + (void)setenv("SHELL", pwd->pw_shell, 1); + + sbuf[0] = '-'; + (void)strlcpy(sbuf + 1, basename(pwd->pw_shell), + sizeof(sbuf) - 1); + shell = sbuf; + } + + (void)execl(pwd->pw_shell, shell, NULL); + err(EXIT_FAILURE, "%s", pwd->pw_shell); + /* NOTREACHED */ +} diff --git a/usr.bin/nice/nice.1 b/usr.bin/nice/nice.1 new file mode 100644 index 0000000..87c211a --- /dev/null +++ b/usr.bin/nice/nice.1 @@ -0,0 +1,120 @@ +.\" $NetBSD: nice.1,v 1.15 2012/03/22 07:58:19 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)nice.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt NICE 1 +.Os +.Sh NAME +.Nm nice +.Nd execute a utility with an altered scheduling priority +.Sh SYNOPSIS +.Nm +.Op Fl n Ar increment +.Ar utility +.Op Ar argument ... +.Sh DESCRIPTION +.Nm +runs +.Ar utility +at an altered scheduling priority. +If an +.Ar increment +is given, it is used; otherwise +an increment of 10 is assumed. +The super-user can run utilities with priorities higher than normal by using +a negative +.Ar increment . +The priority can be adjusted over a +range of -20 (the highest) to 20 (the lowest). +A priority of 19 or 20 +will prevent a process from taking any cycles from others at nice 0 or +better. +.Pp +Available options: +.Bl -tag -width indent +.It Fl n Ar increment +A positive or negative decimal integer used to modify the system scheduling +priority of +.Ar utility . +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width indent +.It 1-125 +An error occurred in the +.Nm +utility. +.It 126 +The +.Ar utility +was found but could not be invoked. +.It 127 +The +.Ar utility +could not be found. +.El +.Pp +Otherwise, the exit status of +.Nm +will be that of +.Ar utility . +.Sh COMPATIBILITY +The historic +.Fl Ns Ar increment +option has been deprecated but is still supported in this implementation. +.Sh SEE ALSO +.Xr csh 1 , +.Xr getpriority 2 , +.Xr setpriority 2 , +.Xr renice 8 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v6 . +.Sh BUGS +.Nm +is built into +.Xr csh 1 +with a slightly different syntax than described here. +The form +.Ql nice +10 +nices to positive nice, and +.Ql nice \-10 +can be used +by the super-user to give a process more of the processor. diff --git a/usr.bin/nice/nice.c b/usr.bin/nice/nice.c new file mode 100644 index 0000000..de5fe5b --- /dev/null +++ b/usr.bin/nice/nice.c @@ -0,0 +1,123 @@ +/* $NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $ */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)nice.c 5.4 (Berkeley) 6/1/90"; +#endif +__RCSID("$NetBSD: nice.c,v 1.15 2008/07/21 14:19:24 lukem Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFNICE 10 + +static void usage(void) __dead; + +int +main(int argc, char **argv) +{ + char *ep; + int niceness = DEFNICE; + int c; + long tmp; + + setprogname(argv[0]); + (void)setlocale(LC_ALL, ""); + + /* handle obsolete -number syntax */ + if (argc > 1 && argv[1][0] == '-' && + isdigit((unsigned char)argv[1][1])) { + niceness = atoi (argv[1] + 1); + argc--; argv++; + } + + while ((c = getopt (argc, argv, "n:")) != -1) { + switch (c) { + case 'n': + errno = 0; + tmp = strtol(optarg, &ep, 10); + if (*ep != '\0' || tmp < INT_MIN || tmp > INT_MAX) + errx(EXIT_FAILURE, "invalid argument: `%s'", + optarg); + niceness = (int)tmp; + break; + default: + usage(); + break; + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + errno = 0; + niceness += getpriority(PRIO_PROCESS, 0); + if (errno) { + err(EXIT_FAILURE, "getpriority"); + /* NOTREACHED */ + } + if (setpriority(PRIO_PROCESS, 0, niceness) == -1) { + warn("setpriority"); + } + + (void)execvp(argv[0], &argv[0]); + err((errno == ENOENT || errno == ENOTDIR) ? 127 : 126, "%s", argv[0]); + /* NOTREACHED */ +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [ -n increment ] utility [ argument ...]\n", + getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/nl/nl.1 b/usr.bin/nl/nl.1 new file mode 100644 index 0000000..d795390 --- /dev/null +++ b/usr.bin/nl/nl.1 @@ -0,0 +1,214 @@ +.\" $NetBSD: nl.1,v 1.16 2013/09/24 22:08:06 wiz Exp $ +.\" +.\" Copyright (c) 1999 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Klaus Klein. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd May 1, 2013 +.Dt NL 1 +.Os +.Sh NAME +.Nm nl +.Nd line numbering filter +.Sh SYNOPSIS +.Nm +.Op Fl p +.Op Fl b Ar type +.Op Fl d Ar delim +.Op Fl f Ar type +.Op Fl h Ar type +.Op Fl i Ar incr +.Op Fl l Ar num +.Op Fl n Ar format +.Op Fl s Ar sep +.Op Fl v Ar startnum +.Op Fl w Ar width +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility reads lines from the named +.Ar file , +applies a configurable line numbering filter operation, +and writes the result to the standard output. +If +.Ar file +is a single dash +.Pq Sq \&- +or absent, +.Nm +reads from the standard input. +.Pp +The +.Nm +utility treats the text it reads in terms of logical pages. +Unless specified otherwise, line numbering is reset at the start of each +logical page. +A logical page consists of a header, a body and a footer section; empty +sections are valid. +Different line numbering options are independently available for header, +body and footer sections. +.Pp +The starts of logical page sections are signaled by input lines containing +nothing but one of the following sequences of delimiter characters: +.Bl -column "\e:\e:\e: " "header " -offset indent +.It Em "Line" Ta Em "Start of" +.It \e:\e:\e: header +.It \e:\e: body +.It \e: footer +.El +.Pp +If the input does not contain any logical page section signaling directives, +the text being read is assumed to consist of a single logical page body. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl b Ar type +Specify the logical page body lines to be numbered. +Recognized +.Ar type +arguments are: +.Bl -tag -width pstringXX +.It a +Number all lines. +.It t +Number only non-empty lines. +.It n +No line numbering. +.It p Ns Ar expr +Number only those lines that contain the basic regular expression specified +by +.Ar expr . +.El +.Pp +The default +.Ar type +for logical page body lines is t. +.It Fl d Ar delim +Specify the delimiter characters used to indicate the start of a logical +page section in the input file. +At most two characters may be specified; if only one character is specified, +the first character is replaced and the second character remains unchanged. +The default +.Ar delim +characters are ``\e:''. +.It Fl f Ar type +Specify the same as +.Fl b Ar type +except for logical page footer lines. +The default +.Ar type +for logical page footer lines is n. +.It Fl h Ar type +Specify the same as +.Fl b Ar type +except for logical page header lines. +The default +.Ar type +for logical page header lines is n. +.It Fl i Ar incr +Specify the increment value used to number logical page lines. +The default +.Ar incr +value is 1. +.It Fl l Ar num +If numbering of all lines is specified for the current logical section +using the corresponding +.Fl b +a, +.Fl f +a +or +.Fl h +a +option, +specify the number of adjacent blank lines to be considered as one. +For example, +.Fl l +2 results in only the second adjacent blank line being numbered. +The default +.Ar num +value is 1. +.It Fl n Ar format +Specify the line numbering output format. +Recognized +.Ar format +arguments are: +.Pp +.Bl -tag -width lnXX -compact -offset indent +.It ln +Left justified. +.It rn +Right justified, leading zeros suppressed. +.It rz +Right justified, leading zeros kept. +.El +.Pp +The default +.Ar format +is rn. +.It Fl p +Specify that line numbering should not be restarted at logical page delimiters. +.It Fl s Ar sep +Specify the characters used in separating the line number and the corresponding +text line. +The default +.Ar sep +setting is a single tab character. +.It Fl v Ar startnum +Specify the initial value used to number logical page lines; see also the +description of the +.Fl p +option. +The default +.Ar startnum +value is 1. +.It Fl w Ar width +Specify the number of characters to be occupied by the line number; +if the +.Ar width +is insufficient to hold the line number, it will be truncated to its +.Ar width +least significant digits. +The default +.Ar width +is 6. +.El +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr pr 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -xpg4.2 +specification. +.Sh HISTORY +The +.Nm +utility first appeared in +.At V.2 . diff --git a/usr.bin/nl/nl.c b/usr.bin/nl/nl.c new file mode 100644 index 0000000..14054ad --- /dev/null +++ b/usr.bin/nl/nl.c @@ -0,0 +1,406 @@ +/* $NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Klaus Klein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1999\ + The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: nl.c,v 1.12 2013/09/17 20:00:50 wiz Exp $"); +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum { + number_all, /* number all lines */ + number_nonempty, /* number non-empty lines */ + number_none, /* no line numbering */ + number_regex /* number lines matching regular expression */ +} numbering_type; + +struct numbering_property { + const char * const name; /* for diagnostics */ + numbering_type type; /* numbering type */ + regex_t expr; /* for type == number_regex */ +}; + +/* line numbering formats */ +#define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */ +#define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */ +#define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */ + +#define FOOTER 0 +#define BODY 1 +#define HEADER 2 +#define NP_LAST HEADER + +static struct numbering_property numbering_properties[NP_LAST + 1] = { + { "footer", number_none, { 0, 0, 0, 0 } }, + { "body", number_nonempty, { 0, 0, 0, 0 } }, + { "header", number_none, { 0, 0, 0, 0 } }, +}; + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +/* + * Maximum number of characters required for a decimal representation of a + * (signed) int; courtesy of tzcode. + */ +#define INT_STRLEN_MAXIMUM \ + ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2) + +static void filter(void); +static void parse_numbering(const char *, int); +static void usage(void) __attribute__((__noreturn__)); + +/* + * Pointer to dynamically allocated input line buffer, and its size. + */ +static char *buffer; +static size_t buffersize; + +/* + * Dynamically allocated buffer suitable for string representation of ints. + */ +static char *intbuffer; +static size_t intbuffersize; + +/* + * Configurable parameters. + */ +/* delimiter characters that indicate the start of a logical page section */ +static char delim[2] = { '\\', ':' }; + +/* line numbering format */ +static const char *format = FORMAT_RN; + +/* increment value used to number logical page lines */ +static int incr = 1; + +/* number of adjacent blank lines to be considered (and numbered) as one */ +static unsigned int nblank = 1; + +/* whether to restart numbering at logical page delimiters */ +static int restart = 1; + +/* characters used in separating the line number and the corrsp. text line */ +static const char *sep = "\t"; + +/* initial value used to number logical page lines */ +static int startnum = 1; + +/* number of characters to be used for the line number */ +/* should be unsigned but required signed by `*' precision conversion */ +static int width = 6; + + +int +main(int argc, char *argv[]) +{ + int c; + long val; + unsigned long uval; + char *ep; + + (void)setlocale(LC_ALL, ""); + + /* + * Note: this implementation strictly conforms to the XBD Utility + * Syntax Guidelines and does not permit the optional `file' operand + * to be intermingled with the options, which is defined in the + * XCU specification (Issue 5) but declared an obsolescent feature that + * will be removed from a future issue. It shouldn't matter, though. + */ + while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) { + switch (c) { + case 'p': + restart = 0; + break; + case 'b': + parse_numbering(optarg, BODY); + break; + case 'd': + if (optarg[0] != '\0') + delim[0] = optarg[0]; + if (optarg[1] != '\0') + delim[1] = optarg[1]; + /* at most two delimiter characters */ + if (optarg[2] != '\0') { + errx(EXIT_FAILURE, + "invalid delim argument -- %s", + optarg); + /* NOTREACHED */ + } + break; + case 'f': + parse_numbering(optarg, FOOTER); + break; + case 'h': + parse_numbering(optarg, HEADER); + break; + case 'i': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) + errx(EXIT_FAILURE, + "invalid incr argument -- %s", optarg); + incr = (int)val; + break; + case 'l': + errno = 0; + uval = strtoul(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + (uval == ULONG_MAX && errno != 0)) + errx(EXIT_FAILURE, + "invalid num argument -- %s", optarg); + nblank = (unsigned int)uval; + break; + case 'n': + if (strcmp(optarg, "ln") == 0) { + format = FORMAT_LN; + } else if (strcmp(optarg, "rn") == 0) { + format = FORMAT_RN; + } else if (strcmp(optarg, "rz") == 0) { + format = FORMAT_RZ; + } else + errx(EXIT_FAILURE, + "illegal format -- %s", optarg); + break; + case 's': + sep = optarg; + break; + case 'v': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) + errx(EXIT_FAILURE, + "invalid startnum value -- %s", optarg); + startnum = (int)val; + break; + case 'w': + errno = 0; + val = strtol(optarg, &ep, 10); + if ((ep != NULL && *ep != '\0') || + ((val == LONG_MIN || val == LONG_MAX) && errno != 0)) + errx(EXIT_FAILURE, + "invalid width value -- %s", optarg); + width = (int)val; + if (!(width > 0)) + errx(EXIT_FAILURE, + "width argument must be > 0 -- %d", + width); + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + break; + case 1: + if (strcmp(argv[0], "-") != 0 && + freopen(argv[0], "r", stdin) == NULL) + err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); + break; + default: + usage(); + /* NOTREACHED */ + } + + /* Determine the maximum input line length to operate on. */ + if ((val = sysconf(_SC_LINE_MAX)) == -1) /* ignore errno */ + val = LINE_MAX; + /* Allocate sufficient buffer space (including the terminating NUL). */ + buffersize = (size_t)val + 1; + if ((buffer = malloc(buffersize)) == NULL) + err(EXIT_FAILURE, "Cannot allocate input line buffer"); + + /* Allocate a buffer suitable for preformatting line number. */ + intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */ + if ((intbuffer = malloc(intbuffersize)) == NULL) + err(EXIT_FAILURE, "cannot allocate preformatting buffer"); + + /* Do the work. */ + filter(); + + return EXIT_SUCCESS; + /* NOTREACHED */ +} + +static void +filter(void) +{ + int line; /* logical line number */ + int section; /* logical page section */ + unsigned int adjblank; /* adjacent blank lines */ + int consumed; /* intbuffer measurement */ + int donumber, idx; + + adjblank = 0; + line = startnum; + section = BODY; +#ifdef __GNUC__ + donumber = 0; /* avoid bogus `uninitialized' warning */ +#endif + + while (fgets(buffer, (int)buffersize, stdin) != NULL) { + for (idx = FOOTER; idx <= NP_LAST; idx++) { + /* Does it look like a delimiter? */ + if (buffer[2 * idx + 0] == delim[0] && + buffer[2 * idx + 1] == delim[1]) { + /* Was this the whole line? */ + if (buffer[2 * idx + 2] == '\n') { + section = idx; + adjblank = 0; + if (restart) + line = startnum; + goto nextline; + } + } else { + break; + } + } + + switch (numbering_properties[section].type) { + case number_all: + /* + * Doing this for number_all only is disputable, but + * the standard expresses an explicit dependency on + * `-b a' etc. + */ + if (buffer[0] == '\n' && ++adjblank < nblank) + donumber = 0; + else + donumber = 1, adjblank = 0; + break; + case number_nonempty: + donumber = (buffer[0] != '\n'); + break; + case number_none: + donumber = 0; + break; + case number_regex: + donumber = + (regexec(&numbering_properties[section].expr, + buffer, 0, NULL, 0) == 0); + break; + } + + if (donumber) { + consumed = snprintf(intbuffer, intbuffersize, format, + width, line); + (void)printf("%s", + intbuffer + max(0, consumed - width)); + line += incr; + } else { + (void)printf("%*s", width, ""); + } + (void)printf("%s%s", sep, buffer); + + if (ferror(stdout)) + err(EXIT_FAILURE, "output error"); +nextline: + ; + } + + if (ferror(stdin)) + err(EXIT_FAILURE, "input error"); +} + +/* + * Various support functions. + */ + +static void +parse_numbering(const char *argstr, int section) +{ + int error; + char errorbuf[NL_TEXTMAX]; + + switch (argstr[0]) { + case 'a': + numbering_properties[section].type = number_all; + break; + case 'n': + numbering_properties[section].type = number_none; + break; + case 't': + numbering_properties[section].type = number_nonempty; + break; + case 'p': + /* If there was a previous expression, throw it away. */ + if (numbering_properties[section].type == number_regex) + regfree(&numbering_properties[section].expr); + else + numbering_properties[section].type = number_regex; + + /* Compile/validate the supplied regular expression. */ + if ((error = regcomp(&numbering_properties[section].expr, + &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) { + (void)regerror(error, + &numbering_properties[section].expr, + errorbuf, sizeof (errorbuf)); + errx(EXIT_FAILURE, + "%s expr: %s -- %s", + numbering_properties[section].name, errorbuf, + &argstr[1]); + } + break; + default: + errx(EXIT_FAILURE, + "illegal %s line numbering type -- %s", + numbering_properties[section].name, argstr); + exit(EXIT_FAILURE); + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-p] [-b type] [-d delim] [-f type] " + "[-h type] [-i incr] [-l num]\n\t[-n format] [-s sep] " + "[-v startnum] [-w width] [file]\n", getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/nohup/nohup.1 b/usr.bin/nohup/nohup.1 new file mode 100644 index 0000000..71a76f1 --- /dev/null +++ b/usr.bin/nohup/nohup.1 @@ -0,0 +1,115 @@ +.\" $NetBSD: nohup.1,v 1.15 2012/03/29 18:33:19 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)nohup.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd July 15, 2005 +.Dt NOHUP 1 +.Os +.Sh NAME +.Nm nohup +.Nd invoke a command immune to hangups +.Sh SYNOPSIS +.Nm +.Ar utility +.Op Ar arg ... +.Sh DESCRIPTION +The +.Nm +command allows the specified utility to be protected from termination +if the user should become logged out +(for example, due to a modem line or TCP/IP connection being dropped). +To do this, +.Nm +sets the +.Dv SIGHUP +.Xr signal 3 +.Pq Dq terminal line hangup +to be ignored, +then executes +.Ar utility +along with any arguments. +.Pp +If the standard output is a terminal, the standard output is +appended to the file +.Pa nohup.out +in the current directory. +If standard error is a terminal, it is directed to the same place +as the standard output. +If the output file +.Pa nohup.out +cannot be created in the current directory, +.Nm +attempts to create the file in the user's home directory. +If the file +.Pa nohup.out +cannot be created, +either in the current directory or the user's home directory, +.Nm +will exit without invoking +.Ar utility , +with an exit value as described below. +.Sh ENVIRONMENT +The following variable is used by +.Nm : +.Bl -tag -width flag +.It Ev HOME +User's home directory. +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width Ds +.It 126 +The +.Ar utility +was found but could not be invoked. +.It 127 +The +.Ar utility +could not be found or an error occurred in +.Nm . +.El +.Pp +Otherwise, the exit status of +.Nm +will be that of +.Ar utility . +.Sh SEE ALSO +.Xr signal 3 +.Sh STANDARDS +The +.Nm +command is expected to be +.St -p1003.2 +compatible. diff --git a/usr.bin/nohup/nohup.c b/usr.bin/nohup/nohup.c new file mode 100644 index 0000000..d177c51 --- /dev/null +++ b/usr.bin/nohup/nohup.c @@ -0,0 +1,142 @@ +/* $NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $ */ + +/* + * Copyright (c) 1989 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)nohup.c 5.4 (Berkeley) 6/1/90"; +#endif +__RCSID("$NetBSD: nohup.c,v 1.15 2011/09/06 18:24:15 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void dofile(void); +__dead static void usage(void); + +/* nohup shall exit with one of the following values: + 126 - The utility was found but could not be invoked. + 127 - An error occurred in the nohup utility, or the utility could + not be found. */ +#define EXIT_NOEXEC 126 +#define EXIT_NOTFOUND 127 +#define EXIT_MISC 127 + +int +main(int argc, char **argv) +{ + int exit_status; + + while (getopt(argc, argv, "") != -1) { + usage(); + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + if (isatty(STDOUT_FILENO)) + dofile(); + if (isatty(STDERR_FILENO) && dup2(STDOUT_FILENO, STDERR_FILENO) == -1) { + /* may have just closed stderr */ + (void)fprintf(stdin, "nohup: %s\n", strerror(errno)); + exit(EXIT_MISC); + } + + /* The nohup utility shall take the standard action for all signals + except that SIGHUP shall be ignored. */ + (void)signal(SIGHUP, SIG_IGN); + + execvp(argv[0], &argv[0]); + exit_status = (errno == ENOENT) ? EXIT_NOTFOUND : EXIT_NOEXEC; + (void)fprintf(stderr, "nohup: %s: %s\n", argv[0], strerror(errno)); + exit(exit_status); +} + +static void +dofile(void) +{ + int fd; + char path[MAXPATHLEN]; + const char *p; + + /* If the standard output is a terminal, all output written to + its standard output shall be appended to the end of the file + nohup.out in the current directory. If nohup.out cannot be + created or opened for appending, the output shall be appended + to the end of the file nohup.out in the directory specified + by the HOME environment variable. + + If a file is created, the file's permission bits shall be + set to S_IRUSR | S_IWUSR. */ +#define FILENAME "nohup.out" + p = FILENAME; + if ((fd = open(p, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0) + goto dupit; + if ((p = getenv("HOME")) != NULL) { + (void)strlcpy(path, p, sizeof(path)); + (void)strlcat(path, "/", sizeof(path)); + (void)strlcat(path, FILENAME, sizeof(path)); + if ((fd = open(p = path, O_RDWR|O_CREAT|O_APPEND, S_IRUSR|S_IWUSR)) >= 0) + goto dupit; + } + (void)fprintf(stderr, "nohup: can't open a nohup.out file.\n"); + exit(EXIT_MISC); + +dupit: (void)lseek(fd, 0L, SEEK_END); + if (dup2(fd, STDOUT_FILENO) == -1) { + (void)fprintf(stderr, "nohup: %s\n", strerror(errno)); + exit(EXIT_MISC); + } + (void)fprintf(stderr, "sending output to %s\n", p); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: nohup utility [argument ...]\n"); + exit(EXIT_MISC); +} diff --git a/usr.bin/paste/paste.1 b/usr.bin/paste/paste.1 new file mode 100644 index 0000000..d76ce5d --- /dev/null +++ b/usr.bin/paste/paste.1 @@ -0,0 +1,116 @@ +.\" $NetBSD: paste.1,v 1.9 2013/04/07 17:43:01 wiz Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Adam S. Moskowitz and the Institute of Electrical and Electronics +.\" Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)paste.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt PASTE 1 +.Os +.Sh NAME +.Nm paste +.Nd merge corresponding or subsequent lines of files +.Sh SYNOPSIS +.Nm +.Op Fl s +.Op Fl d Ar list +.Ar file ... +.Sh DESCRIPTION +The +.Nm +utility concatenates the corresponding lines of the given input files, +replacing all but the last file's newline characters with a single tab +character, and writes the resulting lines to standard output. +If end-of-file is reached on an input file while other input files +still contain data, the file is treated as if it were an endless source +of empty lines. +.Pp +The options are as follows: +.Bl -tag -width Fl +.It Fl d Ar list +Use one or more of the provided characters to replace the newline +characters instead of the default tab. +The characters in +.Ar list +are used circularly, i.e., when +.Ar list +is exhausted the first character from +.Ar list +is reused. +This continues until a line from the last input file (in default operation) +or the last line in each file (using the -s option) is displayed, at which +time +.Nm +begins selecting characters from the beginning of +.Ar list +again. +.Pp +The following special characters can also be used in list: +.Pp +.Bl -tag -width flag -compact +.It Li \en +newline character +.It Li \et +tab character +.It Li \e\e +backslash character +.It Li \e0 +Empty string (not a null character). +.El +.Pp +Any other character preceded by a backslash is equivalent to the +character itself. +.It Fl s +Concatenate all of the lines of each separate input file in command line +order. +The newline character of every line except the last line in each input +file is replaced with the tab character, unless otherwise specified by +the -d option. +.El +.Pp +If +.Ql Fl +is specified for one or more of the input files, the standard +input is used; standard input is read one line at a time, circularly, +for each instance of +.Ql Fl . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr cut 1 , +.Xr join 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. diff --git a/usr.bin/paste/paste.c b/usr.bin/paste/paste.c new file mode 100644 index 0000000..5698131 --- /dev/null +++ b/usr.bin/paste/paste.c @@ -0,0 +1,233 @@ +/* $NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Adam S. Moskowitz of Menlo Consulting. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +/*static char sccsid[] = "from: @(#)paste.c 8.1 (Berkeley) 6/6/93";*/ +__RCSID("$NetBSD: paste.c,v 1.16 2011/09/06 18:24:43 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void parallel(int, char **); +static void sequential(char **); +static int tr(char *); +__dead static void usage(void); + +static char dflt_delim[] = "\t"; +static char *delim = dflt_delim; +static int delimcnt = 1; + +int +main(int argc, char **argv) +{ + int ch, seq; + + seq = 0; + while ((ch = getopt(argc, argv, "d:s")) != -1) { + switch (ch) { + case 'd': + delim = strdup(optarg); + delimcnt = tr(delim); + break; + case 's': + seq = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (seq) + sequential(argv); + else + parallel(argc, argv); + exit(0); +} + +static void +parallel(int argc, char **argv) +{ + char ch, *dp, *line; + FILE **fpp, *fp; + size_t line_len; + int cnt, output; + + fpp = calloc(argc, sizeof *fpp); + if (fpp == NULL) + err(1, "calloc"); + + for (cnt = 0; cnt < argc; cnt++) { + if (strcmp(argv[cnt], "-") == 0) + fpp[cnt] = stdin; + else if (!(fpp[cnt] = fopen(argv[cnt], "r"))) + err(1, "%s", argv[cnt]); + } + + for (;;) { + /* Start with the NUL at the end of 'delim' ... */ + dp = delim + delimcnt; + output = 0; + for (cnt = 0; cnt < argc; cnt++) { + fp = fpp[cnt]; + if (fp == NULL) + continue; + line = fgetln(fp, &line_len); + if (line == NULL) { + /* Assume EOF */ + if (fp != stdin) + fclose(fp); + fpp[cnt] = NULL; + continue; + } + /* Output enough separators to catch up */ + do { + ch = *dp++; + if (ch) + putchar(ch); + if (dp >= delim + delimcnt) + dp = delim; + } while (++output <= cnt); + /* Remove any trailing newline - check for last line */ + if (line[line_len - 1] == '\n') + line_len--; + printf("%.*s", (int)line_len, line); + } + + if (!output) + break; + + /* Add separators to end of line */ + while (++output <= cnt) { + ch = *dp++; + if (ch) + putchar(ch); + if (dp >= delim + delimcnt) + dp = delim; + } + putchar('\n'); + } + + free(fpp); +} + +static void +sequential(char **argv) +{ + FILE *fp; + int cnt; + char ch, *p, *dp; + char buf[_POSIX2_LINE_MAX + 1]; + + for (; (p = *argv) != NULL; ++argv) { + if (p[0] == '-' && !p[1]) + fp = stdin; + else if (!(fp = fopen(p, "r"))) { + warn("%s", p); + continue; + } + if (fgets(buf, sizeof(buf), fp)) { + for (cnt = 0, dp = delim;;) { + if (!(p = strchr(buf, '\n'))) + err(1, "%s: input line too long.", + *argv); + *p = '\0'; + (void)printf("%s", buf); + if (!fgets(buf, sizeof(buf), fp)) + break; + if ((ch = *dp++) != 0) + putchar(ch); + if (++cnt == delimcnt) { + dp = delim; + cnt = 0; + } + } + putchar('\n'); + } + if (fp != stdin) + (void)fclose(fp); + } +} + +static int +tr(char *arg) +{ + int cnt; + char ch, *p; + + for (p = arg, cnt = 0; (ch = *p++); ++arg, ++cnt) + if (ch == '\\') + switch(ch = *p++) { + case 'n': + *arg = '\n'; + break; + case 't': + *arg = '\t'; + break; + case '0': + *arg = '\0'; + break; + default: + *arg = ch; + break; + } else + *arg = ch; + + if (!cnt) + errx(1, "no delimiters specified."); + *arg = '\0'; + return(cnt); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "paste: [-s] [-d delimiters] file ...\n"); + exit(1); +} diff --git a/usr.bin/patch/backupfile.c b/usr.bin/patch/backupfile.c new file mode 100644 index 0000000..25d6076 --- /dev/null +++ b/usr.bin/patch/backupfile.c @@ -0,0 +1,254 @@ +/* + * $OpenBSD: backupfile.c,v 1.19 2006/03/11 19:41:30 otto Exp $ + * $DragonFly: src/usr.bin/patch/backupfile.c,v 1.5 2008/08/11 00:05:06 joerg Exp $ + * $NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $ + */ + +/* + * backupfile.c -- make Emacs style backup file names + * + * Copyright (C) 1990 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* + * David MacKenzie . Some algorithms adapted from GNU Emacs. + */ + +#include +__RCSID("$NetBSD: backupfile.c,v 1.15 2014/04/11 17:30:03 christos Exp $"); + +#include +#include +#include +#include +#include +#include +#include + +#include "backupfile.h" + + +#define ISDIGIT(c) (isascii ((unsigned char)c) && isdigit ((unsigned char)c)) + +/* Which type of backup file names are generated. */ +enum backup_type backup_type = none; + +/* + * The extension added to file names to produce a simple (as opposed to + * numbered) backup file name. + */ +const char *simple_backup_suffix = "~"; + +static char *concat(const char *, const char *); +static char *make_version_name(const char *, int); +static int max_backup_version(const char *, const char *); +static int version_number(const char *, const char *, size_t); +static int argmatch(const char *, const char **); +static void invalid_arg(const char *, const char *, int); + +/* + * Return the name of the new backup file for file FILE, allocated with + * malloc. Return 0 if out of memory. FILE must not end with a '/' unless it + * is the root directory. Do not call this function if backup_type == none. + */ +char * +find_backup_file_name(const char *file) +{ + char *dir, *base_versions, *tmp_file; + int highest_backup; + + if (backup_type == simple) + return concat(file, simple_backup_suffix); + tmp_file = strdup(file); + if (tmp_file == NULL) + return NULL; + base_versions = concat(basename(tmp_file), ".~"); + free(tmp_file); + if (base_versions == NULL) + return NULL; + tmp_file = strdup(file); + if (tmp_file == NULL) { + free(base_versions); + return NULL; + } + dir = dirname(tmp_file); + if (dir == NULL) { + free(base_versions); + free(tmp_file); + return NULL; + } + highest_backup = max_backup_version(base_versions, dir); + free(base_versions); + free(tmp_file); + if (backup_type == numbered_existing && highest_backup == 0) + return concat(file, simple_backup_suffix); + return make_version_name(file, highest_backup + 1); +} + +/* + * Return the number of the highest-numbered backup file for file FILE in + * directory DIR. If there are no numbered backups of FILE in DIR, or an + * error occurs reading DIR, return 0. FILE should already have ".~" appended + * to it. + */ +static int +max_backup_version(const char *file, const char *dir) +{ + DIR *dirp; + struct dirent *dp; + int highest_version, this_version; + size_t file_name_length; + + dirp = opendir(dir); + if (dirp == NULL) + return 0; + + highest_version = 0; + file_name_length = strlen(file); + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_namlen <= file_name_length) + continue; + + this_version = version_number(file, dp->d_name, file_name_length); + if (this_version > highest_version) + highest_version = this_version; + } + closedir(dirp); + return highest_version; +} + +/* + * Return a string, allocated with malloc, containing "FILE.~VERSION~". + * Return 0 if out of memory. + */ +static char * +make_version_name(const char *file, int version) +{ + char *backup_name; + + if (asprintf(&backup_name, "%s.~%d~", file, version) == -1) + return NULL; + return backup_name; +} + +/* + * If BACKUP is a numbered backup of BASE, return its version number; + * otherwise return 0. BASE_LENGTH is the length of BASE. BASE should + * already have ".~" appended to it. + */ +static int +version_number(const char *base, const char *backup, size_t base_length) +{ + int version; + const char *p; + + version = 0; + if (!strncmp(base, backup, base_length) && ISDIGIT(backup[base_length])) { + for (p = &backup[base_length]; ISDIGIT(*p); ++p) + version = version * 10 + *p - '0'; + if (p[0] != '~' || p[1]) + version = 0; + } + return version; +} + +/* + * Return the newly-allocated concatenation of STR1 and STR2. If out of + * memory, return 0. + */ +static char * +concat(const char *str1, const char *str2) +{ + char *newstr; + + if (asprintf(&newstr, "%s%s", str1, str2) == -1) + return NULL; + return newstr; +} + +/* + * If ARG is an unambiguous match for an element of the null-terminated array + * OPTLIST, return the index in OPTLIST of the matched element, else -1 if it + * does not match any element or -2 if it is ambiguous (is a prefix of more + * than one element). + */ +static int +argmatch(const char *arg, const char **optlist) +{ + int i; /* Temporary index in OPTLIST. */ + size_t arglen; /* Length of ARG. */ + int matchind = -1; /* Index of first nonexact match. */ + int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ + + arglen = strlen(arg); + + /* Test all elements for either exact match or abbreviated matches. */ + for (i = 0; optlist[i]; i++) { + if (!strncmp(optlist[i], arg, arglen)) { + if (strlen(optlist[i]) == arglen) + /* Exact match found. */ + return i; + else if (matchind == -1) + /* First nonexact match found. */ + matchind = i; + else + /* Second nonexact match found. */ + ambiguous = 1; + } + } + if (ambiguous) + return -2; + else + return matchind; +} + +/* + * Error reporting for argmatch. KIND is a description of the type of entity + * that was being matched. VALUE is the invalid value that was given. PROBLEM + * is the return value from argmatch. + */ +static void +invalid_arg(const char *kind, const char *value, int problem) +{ + fprintf(stderr, "patch: "); + if (problem == -1) + fprintf(stderr, "invalid"); + else /* Assume -2. */ + fprintf(stderr, "ambiguous"); + fprintf(stderr, " %s `%s'\n", kind, value); +} + +static const char *backup_args[] = { + "none", "never", "simple", "nil", "existing", "t", "numbered", 0 +}; + +static enum backup_type backup_types[] = { + none, simple, simple, numbered_existing, + numbered_existing, numbered, numbered +}; + +/* + * Return the type of backup indicated by VERSION. Unique abbreviations are + * accepted. + */ +enum backup_type +get_version(const char *version) +{ + int i; + + if (version == NULL || *version == '\0') + return numbered_existing; + i = argmatch(version, backup_args); + if (i >= 0) + return backup_types[i]; + invalid_arg("version control type", version, i); + exit(2); +} diff --git a/usr.bin/patch/backupfile.h b/usr.bin/patch/backupfile.h new file mode 100644 index 0000000..d146c3a --- /dev/null +++ b/usr.bin/patch/backupfile.h @@ -0,0 +1,42 @@ +/* + * $OpenBSD: backupfile.h,v 1.6 2003/07/28 18:35:36 otto Exp $ + * $DragonFly: src/usr.bin/patch/backupfile.h,v 1.3 2007/09/29 23:11:10 swildner Exp $ + * $NetBSD: backupfile.h,v 1.6 2008/09/19 18:33:34 joerg Exp $ + */ + +/* + * backupfile.h -- declarations for making Emacs style backup file names + * Copyright (C) 1990 Free Software Foundation, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * without restriction. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. + * + */ + +/* When to make backup files. */ +enum backup_type { + /* Never make backups. */ + none, + + /* Make simple backups of every file. */ + simple, + + /* + * Make numbered backups of files that already have numbered backups, + * and simple backups of the others. + */ + numbered_existing, + + /* Make numbered backups of every file. */ + numbered +}; + +extern enum backup_type backup_type; +extern const char *simple_backup_suffix; + +char *find_backup_file_name(const char *file); +enum backup_type get_version(const char *version); diff --git a/usr.bin/patch/common.h b/usr.bin/patch/common.h new file mode 100644 index 0000000..c19d43b --- /dev/null +++ b/usr.bin/patch/common.h @@ -0,0 +1,123 @@ +/* + * $OpenBSD: common.h,v 1.26 2006/03/11 19:41:30 otto Exp $ + * $DragonFly: src/usr.bin/patch/common.h,v 1.5 2008/08/10 23:50:12 joerg Exp $ + * $NetBSD: common.h,v 1.21 2015/07/24 18:56:44 christos Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#include + +#include +#include + +#define DEBUGGING + +/* constants */ + +#define MAXHUNKSIZE 100000 /* is this enough lines? */ +#define INITHUNKMAX 125 /* initial dynamic allocation size */ +#define MAXLINELEN 8192 +#define BUFFERSIZE 1024 +#define LINENUM_MAX LONG_MAX + +#define SCCSPREFIX "s." +#define GET "get -e %s" +#define SCCSDIFF "get -p %s | diff - %s >/dev/null" + +#define RCSSUFFIX ",v" +#define CHECKOUT "/usr/bin/co" +#define RCSDIFF "/usr/bin/rcsdiff" + +#define ORIGEXT ".orig" +#define REJEXT ".rej" + +/* handy definitions */ + +#define strNE(s1,s2) (strcmp(s1, s2)) +#define strEQ(s1,s2) (!strcmp(s1, s2)) +#define strnNE(s1,s2,l) (strncmp(s1, s2, l)) +#define strnEQ(s1,s2,l) (!strncmp(s1, s2, l)) + +/* typedefs */ + +typedef long LINENUM; /* must be signed */ + +/* globals */ + +extern mode_t filemode; + +extern char buf[MAXLINELEN];/* general purpose buffer */ +extern size_t buf_len; + +extern bool using_plan_a; /* try to keep everything in memory */ +extern bool out_of_mem; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +extern char *filearg[MAXFILEC]; +extern bool ok_to_create_file; +extern char *outname; +extern char *origprae; + +extern char *TMPOUTNAME; +extern char *TMPINNAME; +extern char *TMPREJNAME; +extern char *TMPPATNAME; +extern bool toutkeep; +extern bool trejkeep; + +#ifdef DEBUGGING +extern int debug; +#endif + +extern bool force; +extern bool batch; +extern bool verbose; +extern bool reverse; +extern bool noreverse; +extern bool skip_rest_of_patch; +extern int strippath; +extern bool canonicalize; +/* TRUE if -C was specified on command line. */ +extern bool check_only; +extern bool warn_on_invalid_line; +extern bool last_line_missing_eol; + + +#define CONTEXT_DIFF 1 +#define NORMAL_DIFF 2 +#define ED_DIFF 3 +#define NEW_CONTEXT_DIFF 4 +#define UNI_DIFF 5 + +extern int diff_type; +extern char *revision; /* prerequisite revision, if any */ +extern LINENUM input_lines; /* how long is input file in lines */ + +extern int posix; + diff --git a/usr.bin/patch/inp.c b/usr.bin/patch/inp.c new file mode 100644 index 0000000..b434a88 --- /dev/null +++ b/usr.bin/patch/inp.c @@ -0,0 +1,516 @@ +/* + * $OpenBSD: inp.c,v 1.34 2006/03/11 19:41:30 otto Exp $ + * $DragonFly: src/usr.bin/patch/inp.c,v 1.6 2007/09/29 23:11:10 swildner Exp $ + * $NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#include +__RCSID("$NetBSD: inp.c,v 1.26 2018/06/18 18:33:31 christos Exp $"); + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" + + +/* Input-file-with-indexable-lines abstract type */ + +static off_t i_size; /* size of the input file */ +static char *i_womp; /* plan a buffer for entire file */ +static char **i_ptr; /* pointers to lines in i_womp */ +static char empty_line[] = { '\0' }; + +static int tifd = -1; /* plan b virtual string array */ +static char *tibuf[2]; /* plan b buffers */ +static LINENUM tiline[2] = {-1, -1}; /* 1st line in each buffer */ +static LINENUM lines_per_buf; /* how many lines per buffer */ +static int tireclen; /* length of records in tmp file */ + +static bool rev_in_string(const char *); +static bool reallocate_lines(size_t *); + +/* returns false if insufficient memory */ +static bool plan_a(const char *); + +static void plan_b(const char *); + +/* New patch--prepare to edit another file. */ + +void +re_input(void) +{ + if (using_plan_a) { + i_size = 0; + free(i_ptr); + i_ptr = NULL; + if (i_womp != NULL) { + munmap(i_womp, i_size); + i_womp = NULL; + } + } else { + using_plan_a = true; /* maybe the next one is smaller */ + close(tifd); + tifd = -1; + free(tibuf[0]); + free(tibuf[1]); + tibuf[0] = tibuf[1] = NULL; + tiline[0] = tiline[1] = -1; + tireclen = 0; + } +} + +/* Construct the line index, somehow or other. */ + +void +scan_input(const char *filename) +{ + if (!plan_a(filename)) + plan_b(filename); + if (verbose) { + say("Patching file %s using Plan %s...\n", filename, + (using_plan_a ? "A" : "B")); + } +} + +static bool +reallocate_lines(size_t *lines_allocated) +{ + char **p; + size_t new_size; + + new_size = *lines_allocated * 3 / 2; + p = pch_realloc(i_ptr, new_size + 2, sizeof(char *)); + if (p == NULL) { /* shucks, it was a near thing */ + munmap(i_womp, i_size); + i_womp = NULL; + free(i_ptr); + i_ptr = NULL; + *lines_allocated = 0; + return false; + } + *lines_allocated = new_size; + i_ptr = p; + return true; +} + +/* Try keeping everything in memory. */ + +static bool +plan_a(const char *filename) +{ + int ifd, statfailed, devnull, pstat; + char *p, *s, lbuf[MAXLINELEN]; + struct stat filestat; + off_t i; + ptrdiff_t sz; + size_t iline, lines_allocated; + pid_t pid; + char *argp[4] = {NULL}; + +#ifdef DEBUGGING + if (debug & 8) + return false; +#endif + + if (filename == NULL || *filename == '\0') + return false; + + statfailed = stat(filename, &filestat); + if (statfailed && ok_to_create_file) { + if (verbose) + say("(Creating file %s...)\n", filename); + + /* + * in check_patch case, we still display `Creating file' even + * though we're not. The rule is that -C should be as similar + * to normal patch behavior as possible + */ + if (check_only) + return true; + makedirs(filename, true); + close(creat(filename, 0666)); + statfailed = stat(filename, &filestat); + } + if (statfailed && check_only) + fatal("%s not found, -C mode, can't probe further\n", filename); + /* For nonexistent or read-only files, look for RCS versions. */ + if (statfailed || + /* No one can write to it. */ + (filestat.st_mode & 0222) == 0 || + /* I can't write to it. */ + ((filestat.st_mode & 0022) == 0 && filestat.st_uid != getuid())) { + char *filebase, *filedir; + struct stat cstat; + char *tmp_filename1, *tmp_filename2; + + tmp_filename1 = strdup(filename); + tmp_filename2 = strdup(filename); + if (tmp_filename1 == NULL || tmp_filename2 == NULL) + fatal("strdupping filename"); + + filebase = basename(tmp_filename1); + filedir = dirname(tmp_filename2); + +#define try(f, a1, a2, a3) \ + (snprintf(lbuf, sizeof lbuf, f, a1, a2, a3), stat(lbuf, &cstat) == 0) + + /* + * else we can't write to it but it's not under a version + * control system, so just proceed. + */ + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX)) { + if (!statfailed) { + if ((filestat.st_mode & 0222) != 0) + /* The owner can write to it. */ + fatal("file %s seems to be locked " + "by somebody else under RCS\n", + filename); + /* + * It might be checked out unlocked. See if + * it's safe to check out the default version + * locked. + */ + if (verbose) + say("Comparing file %s to default " + "RCS version...\n", filename); + + switch (pid = fork()) { + case -1: + fatal("can't fork: %s\n", + strerror(errno)); + case 0: + devnull = open("/dev/null", O_RDONLY); + if (devnull == -1) { + fatal("can't open /dev/null: %s", + strerror(errno)); + } + (void)dup2(devnull, STDOUT_FILENO); + argp[0] = __UNCONST(RCSDIFF); + argp[1] = __UNCONST(filename); + execv(RCSDIFF, argp); + exit(127); + } + pid = waitpid(pid, &pstat, 0); + if (pid == -1 || WEXITSTATUS(pstat) != 0) { + fatal("can't check out file %s: " + "differs from default RCS version\n", + filename); + } + } + + if (verbose) + say("Checking out file %s from RCS...\n", + filename); + + switch (pid = fork()) { + case -1: + fatal("can't fork: %s\n", strerror(errno)); + case 0: + argp[0] = __UNCONST(CHECKOUT); + argp[1] = __UNCONST("-l"); + argp[2] = __UNCONST(filename); + execv(CHECKOUT, argp); + exit(127); + } + pid = waitpid(pid, &pstat, 0); + if (pid == -1 || WEXITSTATUS(pstat) != 0 || + stat(filename, &filestat)) { + fatal("can't check out file %s from RCS\n", + filename); + } + } else if (statfailed) { + fatal("can't find %s\n", filename); + } + free(tmp_filename1); + free(tmp_filename2); + } + + filemode = filestat.st_mode; + if (!S_ISREG(filemode)) + fatal("%s is not a normal file--can't patch\n", filename); + i_size = filestat.st_size; + if (out_of_mem) { + set_hunkmax(); /* make sure dynamic arrays are allocated */ + out_of_mem = false; + return false; /* force plan b because plan a bombed */ + } + if ((uintmax_t)i_size > (uintmax_t)SIZE_MAX) { + say("block too large to mmap\n"); + return false; + } + if ((ifd = open(filename, O_RDONLY)) < 0) + pfatal("can't open file %s", filename); + + if (i_size) { + i_womp = mmap(NULL, i_size, PROT_READ, MAP_PRIVATE, ifd, 0); + if (i_womp == MAP_FAILED) { + perror("mmap failed"); + i_womp = NULL; + close(ifd); + return false; + } + } else { + i_womp = NULL; + } + + close(ifd); + if (i_size) + madvise(i_womp, i_size, MADV_SEQUENTIAL); + + /* estimate the number of lines */ + lines_allocated = i_size / 25; + if (lines_allocated < 100) + lines_allocated = 100; + + if (!reallocate_lines(&lines_allocated)) + return false; + + /* now scan the buffer and build pointer array */ + iline = 1; + i_ptr[iline] = i_womp; + /* test for NUL too, to maintain the behavior of the original code */ + for (s = i_womp, i = 0; i < i_size && *s != '\0'; s++, i++) { + if (*s == '\n') { + if (iline == lines_allocated) { + if (!reallocate_lines(&lines_allocated)) + return false; + } + /* these are NOT NUL terminated */ + i_ptr[++iline] = s + 1; + } + } + /* if the last line contains no EOL, append one */ + if (i_size > 0 && i_womp[i_size - 1] != '\n') { + last_line_missing_eol = true; + /* fix last line */ + sz = s - i_ptr[iline]; + p = malloc(sz + 1); + if (p == NULL) { + free(i_ptr); + i_ptr = NULL; + munmap(i_womp, i_size); + i_womp = NULL; + return false; + } + + memcpy(p, i_ptr[iline], sz); + p[sz] = '\n'; + i_ptr[iline] = p; + /* count the extra line and make it point to some valid mem */ + i_ptr[++iline] = empty_line; + } else + last_line_missing_eol = false; + + input_lines = iline - 1; + + /* now check for revision, if any */ + + if (revision != NULL) { + if (!rev_in_string(i_womp)) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the " + "%s version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + return true; /* plan a will work */ +} + +/* Keep (virtually) nothing in memory. */ + +static void +plan_b(const char *filename) +{ + FILE *ifp; + size_t i = 0, j, maxlen = 1; + char *p; + bool found_revision = (revision == NULL); + + using_plan_a = false; + if ((ifp = fopen(filename, "r")) == NULL) + pfatal("can't open file %s", filename); + unlink(TMPINNAME); + if ((tifd = open(TMPINNAME, O_EXCL | O_CREAT | O_WRONLY, 0666)) < 0) + pfatal("can't open file %s", TMPINNAME); + while (fgets(buf, buf_len, ifp) != NULL) { + if (revision != NULL && !found_revision && rev_in_string(buf)) + found_revision = true; + if ((i = strlen(buf)) > maxlen) + maxlen = i; /* find longest line */ + } + last_line_missing_eol = i > 0 && buf[i - 1] != '\n'; + if (last_line_missing_eol && maxlen == i) + maxlen++; + + if (revision != NULL) { + if (!found_revision) { + if (force) { + if (verbose) + say("Warning: this file doesn't appear " + "to be the %s version--patching anyway.\n", + revision); + } else if (batch) { + fatal("this file doesn't appear to be the " + "%s version--aborting.\n", + revision); + } else { + ask("This file doesn't appear to be the %s " + "version--patch anyway? [n] ", + revision); + if (*buf != 'y') + fatal("aborted\n"); + } + } else if (verbose) + say("Good. This file appears to be the %s version.\n", + revision); + } + fseek(ifp, 0L, SEEK_SET); /* rewind file */ + lines_per_buf = BUFFERSIZE / maxlen; + tireclen = maxlen; + tibuf[0] = malloc(BUFFERSIZE + 1); + if (tibuf[0] == NULL) + fatal("out of memory\n"); + tibuf[1] = malloc(BUFFERSIZE + 1); + if (tibuf[1] == NULL) + fatal("out of memory\n"); + for (i = 1;; i++) { + p = tibuf[0] + maxlen * (i % lines_per_buf); + if (i % lines_per_buf == 0) /* new block */ + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + if (fgets(p, maxlen + 1, ifp) == NULL) { + input_lines = i - 1; + if (i % lines_per_buf != 0) + if (write(tifd, tibuf[0], BUFFERSIZE) < BUFFERSIZE) + pfatal("can't write temp file"); + break; + } + j = strlen(p); + /* These are '\n' terminated strings, so no need to add a NUL */ + if (j == 0 || p[j - 1] != '\n') + p[j] = '\n'; + } + fclose(ifp); + close(tifd); + if ((tifd = open(TMPINNAME, O_RDONLY)) < 0) + pfatal("can't reopen file %s", TMPINNAME); +} + +/* + * Fetch a line from the input file, \n terminated, not necessarily \0. + */ +char * +ifetch(LINENUM line, int whichbuf) +{ + if (line < 1 || line > input_lines) { + if (warn_on_invalid_line) { + say("No such line %ld in input file, ignoring\n", line); + warn_on_invalid_line = false; + } + return NULL; + } + if (using_plan_a) + return i_ptr[line]; + else { + LINENUM offline = line % lines_per_buf; + LINENUM baseline = line - offline; + + if (tiline[0] == baseline) + whichbuf = 0; + else if (tiline[1] == baseline) + whichbuf = 1; + else { + tiline[whichbuf] = baseline; + + if (lseek(tifd, (off_t) (baseline / lines_per_buf * + BUFFERSIZE), SEEK_SET) < 0) + pfatal("cannot seek in the temporary input file"); + + if (read(tifd, tibuf[whichbuf], BUFFERSIZE) < 0) + pfatal("error reading tmp file %s", TMPINNAME); + } + return tibuf[whichbuf] + (tireclen * offline); + } +} + +/* + * True if the string argument contains the revision number we want. + */ +static bool +rev_in_string(const char *string) +{ + const char *s; + size_t patlen; + + if (revision == NULL) + return true; + patlen = strlen(revision); + if (strnEQ(string, revision, patlen) && isspace((unsigned char)string[patlen])) + return true; + for (s = string; *s; s++) { + if (isspace((unsigned char)*s) && strnEQ(s + 1, revision, patlen) && + isspace((unsigned char)s[patlen + 1])) { + return true; + } + } + return false; +} diff --git a/usr.bin/patch/inp.h b/usr.bin/patch/inp.h new file mode 100644 index 0000000..d010526 --- /dev/null +++ b/usr.bin/patch/inp.h @@ -0,0 +1,35 @@ +/* + * $OpenBSD: inp.h,v 1.8 2003/08/15 08:00:51 otto Exp $ + * $DragonFly: src/usr.bin/patch/inp.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + * $NetBSD: inp.h,v 1.10 2008/09/19 18:33:34 joerg Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +void re_input(void); +void scan_input(const char *); +char *ifetch(LINENUM, int); diff --git a/usr.bin/patch/mkpath.c b/usr.bin/patch/mkpath.c new file mode 100644 index 0000000..3d18036 --- /dev/null +++ b/usr.bin/patch/mkpath.c @@ -0,0 +1,84 @@ +/* + * $OpenBSD: mkpath.c,v 1.2 2005/06/20 07:14:06 otto Exp $ + * $DragonFly: src/usr.bin/patch/mkpath.c,v 1.1 2007/09/29 23:11:10 swildner Exp $ + * $NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $ + */ + +/* + * Copyright (c) 1983, 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__RCSID("$NetBSD: mkpath.c,v 1.1 2008/09/19 18:33:34 joerg Exp $"); + +#include +#include +#include +#include +#include + +int mkpath(char *); + +/* Code taken directly from mkdir(1). + + * mkpath -- create directories. + * path - path + */ +int +mkpath(char *path) +{ + struct stat sb; + char *slash; + int done = 0; + + slash = path; + + while (!done) { + slash += strspn(slash, "/"); + slash += strcspn(slash, "/"); + + done = (*slash == '\0'); + *slash = '\0'; + + if (stat(path, &sb)) { + if (errno != ENOENT || (mkdir(path, 0777) && + errno != EEXIST)) { + warn("%s", path); + return (-1); + } + } else if (!S_ISDIR(sb.st_mode)) { + warnx("%s: %s", path, strerror(ENOTDIR)); + return (-1); + } + + *slash = '/'; + } + + return (0); +} + diff --git a/usr.bin/patch/patch.1 b/usr.bin/patch/patch.1 new file mode 100644 index 0000000..1f434c7 --- /dev/null +++ b/usr.bin/patch/patch.1 @@ -0,0 +1,663 @@ +.\" $OpenBSD: patch.1,v 1.22 2008/06/06 20:44:00 jmc Exp $ +.\" $DragonFly: src/usr.bin/patch/patch.1,v 1.10 2008/08/18 19:15:55 joerg Exp $ +.\" $NetBSD: patch.1,v 1.21 2017/07/03 21:34:20 wiz Exp $ +.\" Copyright 1986, Larry Wall +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following condition +.\" is met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this condition and the following disclaimer. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd November 7, 2015 +.Dt PATCH 1 +.Os +.Sh NAME +.Nm patch +.Nd apply a diff file to an original +.Sh SYNOPSIS +.Nm +.Op Fl bCcEeflNnRstuv +.Op Fl B Ar backup-prefix +.Op Fl D Ar symbol +.Op Fl d Ar directory +.Op Fl F Ar max-fuzz +.Op Fl i Ar patchfile +.Op Fl o Ar out-file +.Op Fl p Ar strip-count +.Op Fl r Ar rej-name +.Op Fl V Cm t | nil | never | none +.Op Fl x Ar number +.Op Fl z Ar backup-ext +.Op Fl Fl posix +.Op Ar origfile Op Ar patchfile +.Nm +.Pf \*(Lt Ar patchfile +.Sh DESCRIPTION +.Nm +will take a patch file containing any of the four forms of difference +listing produced by the +.Xr diff 1 +program and apply those differences to an original file, +producing a patched version. +If +.Ar patchfile +is omitted, or is a hyphen, the patch will be read from the standard input. +.Pp +.Nm +will attempt to determine the type of the diff listing, unless over-ruled by a +.Fl c , +.Fl e , +.Fl n , +or +.Fl u +option. +Context diffs (old-style, new-style, and unified) and +normal diffs are applied directly by the +.Nm +program itself, whereas ed diffs are simply fed to the +.Xr ed 1 +editor via a pipe. +.Pp +If the +.Ar patchfile +contains more than one patch, +.Nm +will try to apply each of them as if they came from separate patch files. +This means, among other things, that it is assumed that the name of the file +to patch must be determined for each diff listing, and that the garbage before +each diff listing will be examined for interesting things such as file names +and revision level (see the section on +.Sx Filename Determination +below). +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl B Ar backup-prefix , Fl Fl prefix Ar backup-prefix +Causes the next argument to be interpreted as a prefix to the backup file +name. +If this argument is specified, any argument to +.Fl z +will be ignored. +.It Fl b , Fl Fl backup +Save a backup copy of the file before it is modified. +By default the original file is saved with a backup extension of +.Qq .orig +unless the file already has a numbered backup, in which case a numbered +backup is made. +This is equivalent to specifying +.Qo Fl V Cm existing Qc . +This option is currently the default, unless +.Fl -posix +is specified. +.It Fl C , Fl Fl check +Checks that the patch would apply cleanly, but does not modify anything. +.It Fl c , Fl Fl context +Forces +.Nm +to interpret the patch file as a context diff. +.It Fl D Ar symbol , Fl Fl ifdef Ar symbol +Causes +.Nm +to use the +.Qq #ifdef...#endif +construct to mark changes. +The argument following will be used as the differentiating symbol. +Note that, unlike the C compiler, there must be a space between the +.Fl D +and the argument. +.It Fl d Ar directory , Fl Fl directory Ar directory +Causes +.Nm +to interpret the next argument as a directory, +and change the working directory to it before doing anything else. +.It Fl E , Fl Fl remove-empty-files +Causes +.Nm +to remove output files that are empty after the patches have been applied. +This option is useful when applying patches that create or remove files. +.It Fl e , Fl Fl ed +Forces +.Nm +to interpret the patch file as an +.Xr ed 1 +script. +.It Fl F Ar max-fuzz , Fl Fl fuzz Ar max-fuzz +Sets the maximum fuzz factor. +This option only applies to context diffs, and causes +.Nm +to ignore up to that many lines in looking for places to install a hunk. +Note that a larger fuzz factor increases the odds of a faulty patch. +The default fuzz factor is 2, and it may not be set to more than +the number of lines of context in the context diff, ordinarily 3. +.It Fl f , Fl Fl force +Forces +.Nm +to assume that the user knows exactly what he or she is doing, and to not +ask any questions. +It assumes the following: +skip patches for which a file to patch can't be found; +patch files even though they have the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are not reversed even if they look like they are. +This option does not suppress commentary; use +.Fl s +for that. +.It Fl i Ar patchfile , Fl Fl input Ar patchfile +Causes the next argument to be interpreted as the input file name +(i.e., a patchfile). +This option may be specified multiple times. +.It Fl l , Fl Fl ignore-whitespace +Causes the pattern matching to be done loosely, in case the tabs and +spaces have been munged in your input file. +Any sequence of whitespace in the pattern line will match any sequence +in the input file. +Normal characters must still match exactly. +Each line of the context must still match a line in the input file. +.It Fl N , Fl Fl forward +Causes +.Nm +to ignore patches that it thinks are reversed or already applied. +See also +.Fl R . +.It Fl n , Fl Fl normal +Forces +.Nm +to interpret the patch file as a normal diff. +.It Fl o Ar out-file , Fl Fl output Ar out-file +Causes the next argument to be interpreted as the output file name. +.It Fl p Ar strip-count , Fl Fl strip Ar strip-count +Sets the pathname strip count, +which controls how pathnames found in the patch file are treated, +in case you keep your files in a different directory than the person who sent +out the patch. +The strip count specifies how many slashes are to be stripped from +the front of the pathname. +(Any intervening directory names also go away.) +For example, supposing the file name in the patch file was +.Pa /u/howard/src/blurfl/blurfl.c : +.Pp +Setting +.Fl p Ns Ar 0 +gives the entire pathname unmodified. +.Pp +.Fl p Ns Ar 1 +gives +.Pp +.D1 Pa u/howard/src/blurfl/blurfl.c +.Pp +without the leading slash. +.Pp +.Fl p Ns Ar 4 +gives +.Pp +.D1 Pa blurfl/blurfl.c +.Pp +Not specifying +.Fl p +at all just gives you +.Pa blurfl.c , +unless all of the directories in the leading path +.Pq Pa u/howard/src/blurfl +exist and that path is relative, +in which case you get the entire pathname unmodified. +Whatever you end up with is looked for either in the current directory, +or the directory specified by the +.Fl d +option. +.It Fl R , Fl Fl reverse +Tells +.Nm +that this patch was created with the old and new files swapped. +(Yes, I'm afraid that does happen occasionally, human nature being what it +is.) +.Nm +will attempt to swap each hunk around before applying it. +Rejects will come out in the swapped format. +The +.Fl R +option will not work with ed diff scripts because there is too little +information to reconstruct the reverse operation. +.Pp +If the first hunk of a patch fails, +.Nm +will reverse the hunk to see if it can be applied that way. +If it can, you will be asked if you want to have the +.Fl R +option set. +If it can't, the patch will continue to be applied normally. +(Note: this method cannot detect a reversed patch if it is a normal diff +and if the first command is an append (i.e., it should have been a delete) +since appends always succeed, due to the fact that a null context will match +anywhere. +Luckily, most patches add or change lines rather than delete them, so most +reversed normal diffs will begin with a delete, which will fail, triggering +the heuristic.) +.It Fl r Ar rej-name , Fl Fl reject-file Ar rej-name +Causes the next argument to be interpreted as the reject file name. +.It Fl s , Fl Fl quiet , Fl Fl silent +Makes +.Nm +do its work silently, unless an error occurs. +.It Fl t , Fl Fl batch +Similar to +.Fl f , +in that it suppresses questions, but makes some different assumptions: +skip patches for which a file to patch can't be found (the same as +.Fl f ) ; +skip patches for which the file has the wrong version for the +.Qq Prereq: +line in the patch; +and assume that patches are reversed if they look like they are. +.It Fl u , Fl Fl unified +Forces +.Nm +to interpret the patch file as a unified context diff (a unidiff). +.It Fl V Cm t | nil | never | none , Fl Fl version-control Cm t | nil | never | none +Causes the next argument to be interpreted as a method for creating +backup file names. +The type of backups made can also be given in the +.Ev PATCH_VERSION_CONTROL +or +.Ev VERSION_CONTROL +environment variables, which are overridden by this option. +The +.Fl B +option overrides this option, causing the prefix to always be used for +making backup file names. +The values of the +.Ev PATCH_VERSION_CONTROL +and +.Ev VERSION_CONTROL +environment variables and the argument to the +.Fl V +option are like the GNU Emacs +.Dq version-control +variable; they also recognize synonyms that are more descriptive. +The valid values are (unique abbreviations are accepted): +.Bl -tag -width Ds -offset indent +.It Cm t , numbered +Always make numbered backups. +.It Cm nil , existing +Make numbered backups of files that already have them, +simple backups of the others. +.It Cm never , simple +Always make simple backups. +.It Cm none +No backups are created. +.El +.It Fl v , Fl Fl version +Causes +.Nm +to print out its revision header and patch level. +.It Fl x Ar number , Fl Fl debug Ar number +Sets internal debugging flags, and is of interest only to +.Nm +patchers. +.It Fl z Ar backup-ext , Fl Fl suffix Ar backup-ext +Causes the next argument to be interpreted as the backup extension, to be +used in place of +.Qq .orig . +.It Fl Fl posix +Enables strict +.St -p1003.1-2004 +conformance, specifically: +.Bl -enum +.It +Backup files are not created unless the +.Fl b +option is specified. +.It +If unspecified, the file name used is the first of the old, new and +index files that exists. +.El +.El +.Ss Patch Application +.Nm +will try to skip any leading garbage, apply the diff, +and then skip any trailing garbage. +Thus you could feed an article or message containing a +diff listing to +.Nm , +and it should work. +If the entire diff is indented by a consistent amount, +this will be taken into account. +.Pp +With context diffs, and to a lesser extent with normal diffs, +.Nm +can detect when the line numbers mentioned in the patch are incorrect, +and will attempt to find the correct place to apply each hunk of the patch. +As a first guess, it takes the line number mentioned for the hunk, plus or +minus any offset used in applying the previous hunk. +If that is not the correct place, +.Nm +will scan both forwards and backwards for a set of lines matching the context +given in the hunk. +First +.Nm +looks for a place where all lines of the context match. +If no such place is found, and it's a context diff, and the maximum fuzz factor +is set to 1 or more, then another scan takes place ignoring the first and last +line of context. +If that fails, and the maximum fuzz factor is set to 2 or more, +the first two and last two lines of context are ignored, +and another scan is made. +.Pq The default maximum fuzz factor is 2. +.Pp +If +.Nm +cannot find a place to install that hunk of the patch, it will put the hunk +out to a reject file, which normally is the name of the output file plus +.Qq .rej . +(Note that the rejected hunk will come out in context diff form whether the +input patch was a context diff or a normal diff. +If the input was a normal diff, many of the contexts will simply be null.) +The line numbers on the hunks in the reject file may be different than +in the patch file: they reflect the approximate location patch thinks the +failed hunks belong in the new file rather than the old one. +.Pp +As each hunk is completed, you will be told whether the hunk succeeded or +failed, and which line (in the new file) +.Nm +thought the hunk should go on. +If this is different from the line number specified in the diff, +you will be told the offset. +A single large offset MAY be an indication that a hunk was installed in the +wrong place. +You will also be told if a fuzz factor was used to make the match, in which +case you should also be slightly suspicious. +.Ss Filename Determination +If no original file is specified on the command line, +.Nm +will try to figure out from the leading garbage what the name of the file +to edit is. +When checking a prospective file name, pathname components are stripped +as specified by the +.Fl p +option and the file's existence and writability are checked relative +to the current working directory (or the directory specified by the +.Fl d +option). +.Pp +If the diff is a context or unified diff, +.Nm +is able to determine the old and new file names from the diff header. +For context diffs, the +.Dq old +file is specified in the line beginning with +.Qq *** +and the +.Dq new +file is specified in the line beginning with +.Qq --- . +For a unified diff, the +.Dq old +file is specified in the line beginning with +.Qq --- +and the +.Dq new +file is specified in the line beginning with +.Qq +++ . +If there is an +.Qq Index: +line in the leading garbage (regardless of the diff type), +.Nm +will use the file name from that line as the +.Dq index +file. +.Pp +.Nm +will choose the file name by performing the following steps, with the first +match used: +.Bl -enum +.It +If +.Nm +is operating in strict +.St -p1003.1-2004 +mode, the first of the +.Dq old , +.Dq new +and +.Dq index +file names that exist is used. +Otherwise, +.Nm +will examine either the +.Dq old +and +.Dq new +file names or, for a non-context diff, the +.Dq index +file name, and choose the file name with the fewest path components, +the shortest basename, and the shortest total file name length (in that order). +.It +If no file exists, +.Nm +checks for the existence of the files in an RCS directory using the criteria +specified above. +If found, +.Nm +will attempt to get or check out the file. +.It +If no suitable file was found to patch, the patch file is a context or +unified diff, and the old file was zero length, the new file name is +created and used. +.It +If the file name still cannot be determined, +.Nm +will prompt the user for the file name to use. +.El +.Pp +Additionally, if the leading garbage contains a +.Qq Prereq:\ \& +line, +.Nm +will take the first word from the prerequisites line (normally a version +number) and check the input file to see if that word can be found. +If not, +.Nm +will ask for confirmation before proceeding. +.Pp +The upshot of all this is that you should be able to say, while in a news +interface, the following: +.Pp +.Dl | patch -d /usr/src/local/blurfl +.Pp +and patch a file in the blurfl directory directly from the article containing +the patch. +.Ss Backup Files +By default, the patched version is put in place of the original, with +the original file backed up to the same name with the extension +.Qq .orig , +or as specified by the +.Fl B , +.Fl V , +or +.Fl z +options. +The extension used for making backup files may also be specified in the +.Ev SIMPLE_BACKUP_SUFFIX +environment variable, which is overridden by the options above. +.Pp +If the backup file is a symbolic or hard link to the original file, +.Nm +creates a new backup file name by changing the first lowercase letter +in the last component of the file's name into uppercase. +If there are no more lowercase letters in the name, +it removes the first character from the name. +It repeats this process until it comes up with a +backup file that does not already exist or is not linked to the original file. +.Pp +You may also specify where you want the output to go with the +.Fl o +option; if that file already exists, it is backed up first. +.Ss Notes For Patch Senders +There are several things you should bear in mind if you are going to +be sending out patches: +.Pp +First, you can save people a lot of grief by keeping a +.Pa patchlevel.h +file which is patched to increment the patch level as the first diff in the +patch file you send out. +If you put a +.Qq Prereq: +line in with the patch, it won't let them apply +patches out of order without some warning. +.Pp +Second, make sure you've specified the file names right, either in a +context diff header, or with an +.Qq Index: +line. +If you are patching something in a subdirectory, be sure to tell the patch +user to specify a +.Fl p +option as needed. +.Pp +Third, you can create a file by sending out a diff that compares a +null file to the file you want to create. +This will only work if the file you want to create doesn't exist already in +the target directory. +.Pp +Fourth, take care not to send out reversed patches, since it makes people wonder +whether they already applied the patch. +.Pp +Fifth, while you may be able to get away with putting 582 diff listings into +one file, it is probably wiser to group related patches into separate files in +case something goes haywire. +.Sh ENVIRONMENT +.Bl -tag -width "PATCH_VERSION_CONTROL" -compact +.It Ev POSIXLY_CORRECT +When set, +.Nm +behaves as if the +.Fl Fl posix +option has been specified. +.It Ev SIMPLE_BACKUP_SUFFIX +Extension to use for backup file names instead of +.Qq .orig . +.It Ev TMPDIR +Directory to put temporary files in; default is +.Pa /tmp . +.It Ev PATCH_VERSION_CONTROL +Selects when numbered backup files are made. +.It Ev VERSION_CONTROL +Same as +.Ev PATCH_VERSION_CONTROL . +.El +.Sh FILES +.Bl -tag -width "$TMPDIR/patch*" -compact +.It Pa $TMPDIR/patch* +.Nm +temporary files +.It Pa /dev/tty +used to read input when +.Nm +prompts the user +.El +.Sh DIAGNOSTICS +Too many to list here, but generally indicative that +.Nm +couldn't parse your patch file. +.Pp +The message +.Qq Hmm... +indicates that there is unprocessed text in the patch file and that +.Nm +is attempting to intuit whether there is a patch in that text and, if so, +what kind of patch it is. +.Pp +The +.Nm +utility exits with one of the following values: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It \&0 +Successful completion. +.It \&1 +One or more lines were written to a reject file. +.It >\&1 +An error occurred. +.El +.Pp +When applying a set of patches in a loop it behooves you to check this +exit status so you don't apply a later patch to a partially patched file. +.Sh SEE ALSO +.Xr diff 1 +.Sh STANDARDS +The +.Nm +utility is compliant with the +.St -p1003.1-2004 +specification +(except as detailed above for the +.Fl -posix +option), +though the presence of +.Nm +itself is optional. +.Pp +The flags +.Op Fl CEfstuvBFVxz +and +.Op Fl -posix +are extensions to that specification. +.Sh AUTHORS +.An Larry Wall +with many other contributors. +.Sh CAVEATS +.Nm +cannot tell if the line numbers are off in an ed script, and can only detect +bad line numbers in a normal diff when it finds a +.Qq change +or a +.Qq delete +command. +A context diff using fuzz factor 3 may have the same problem. +Until a suitable interactive interface is added, you should probably do +a context diff in these cases to see if the changes made sense. +Of course, compiling without errors is a pretty good indication that the patch +worked, but not always. +.Pp +.Nm +usually produces the correct results, even when it has to do a lot of +guessing. +However, the results are guaranteed to be correct only when the patch is +applied to exactly the same version of the file that the patch was +generated from. +.Sh BUGS +Could be smarter about partial matches, excessively deviant offsets and +swapped code, but that would take an extra pass. +.Pp +Check patch mode +.Pq Fl C +will fail if you try to check several patches in succession that build on +each other. +The entire +.Nm +code would have to be restructured to keep temporary files around so that it +can handle this situation. +.Pp +If code has been duplicated (for instance with #ifdef OLDCODE ... #else ... +#endif), +.Nm +is incapable of patching both versions, and, if it works at all, will likely +patch the wrong one, and tell you that it succeeded to boot. +.Pp +If you apply a patch you've already applied, +.Nm +will think it is a reversed patch, and offer to un-apply the patch. +This could be construed as a feature. diff --git a/usr.bin/patch/patch.c b/usr.bin/patch/patch.c new file mode 100644 index 0000000..9f49e7f --- /dev/null +++ b/usr.bin/patch/patch.c @@ -0,0 +1,1064 @@ +/* + * $OpenBSD: patch.c,v 1.45 2007/04/18 21:52:24 sobrado Exp $ + * $DragonFly: src/usr.bin/patch/patch.c,v 1.10 2008/08/10 23:39:56 joerg Exp $ + * $NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#include +__RCSID("$NetBSD: patch.c,v 1.29 2011/09/06 18:25:14 joerg Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "inp.h" +#include "backupfile.h" +#include "pathnames.h" + +mode_t filemode = 0644; + +char buf[MAXLINELEN]; /* general purpose buffer */ +size_t buf_len = sizeof(buf); + +bool using_plan_a = true; /* try to keep everything in memory */ +bool out_of_mem = false; /* ran out of memory in plan a */ + +#define MAXFILEC 2 + +char *filearg[MAXFILEC]; +bool ok_to_create_file = false; +char *outname = NULL; +char *origprae = NULL; +char *TMPOUTNAME; +char *TMPINNAME; +char *TMPREJNAME; +char *TMPPATNAME; +bool toutkeep = false; +bool trejkeep = false; +bool warn_on_invalid_line; +bool last_line_missing_eol; + +#ifdef DEBUGGING +int debug = 0; +#endif + +bool force = false; +bool batch = false; +bool verbose = true; +bool reverse = false; +bool noreverse = false; +bool skip_rest_of_patch = false; +int strippath = 957; +bool canonicalize = false; +bool check_only = false; +int diff_type = 0; +char *revision = NULL; /* prerequisite revision, if any */ +LINENUM input_lines = 0; /* how long is input file in lines */ +int posix = 0; /* strict POSIX mode? */ + +static void reinitialize_almost_everything(void); +static void get_some_switches(void); +static LINENUM locate_hunk(LINENUM); +static void abort_context_hunk(void); +static void rej_line(int, LINENUM); +static void abort_hunk(void); +static void apply_hunk(LINENUM); +static void init_output(const char *); +static void init_reject(const char *); +static void copy_till(LINENUM, bool); +static bool spew_output(void); +static void dump_line(LINENUM, bool); +static bool patch_match(LINENUM, LINENUM, LINENUM); +static bool similar(const char *, const char *, int); +__dead static void usage(void); + +/* true if -E was specified on command line. */ +static bool remove_empty_files = false; + +/* true if -R was specified on command line. */ +static bool reverse_flag_specified = false; + +/* buffer holding the name of the rejected patch file. */ +static char rejname[NAME_MAX + 1]; + +/* buffer for stderr */ +static char serrbuf[BUFSIZ]; + +/* how many input lines have been irretractibly output */ +static LINENUM last_frozen_line = 0; + +static int Argc; /* guess */ +static char **Argv; +static int Argc_last; /* for restarting plan_b */ +static char **Argv_last; + +static FILE *ofp = NULL; /* output file pointer */ +static FILE *rejfp = NULL; /* reject file pointer */ + +static int filec = 0; /* how many file arguments? */ +static LINENUM last_offset = 0; +static LINENUM maxfuzz = 2; + +/* patch using ifdef, ifndef, etc. */ +static bool do_defines = false; +/* #ifdef xyzzy */ +static char if_defined[128]; +/* #ifndef xyzzy */ +static char not_defined[128]; +/* #else */ +static const char else_defined[] = "#else\n"; +/* #endif xyzzy */ +static char end_defined[128]; + + +/* Apply a set of diffs as appropriate. */ + +int +main(int argc, char *argv[]) +{ + int error = 0, hunk, failed, i, fd; + LINENUM where = 0, newwhere, fuzz, mymaxfuzz; + const char *tmpdir; + char *v; + + setbuf(stderr, serrbuf); + for (i = 0; i < MAXFILEC; i++) + filearg[i] = NULL; + + /* Cons up the names of the temporary files. */ + if ((tmpdir = getenv("TMPDIR")) == NULL || *tmpdir == '\0') + tmpdir = _PATH_TMP; + for (i = strlen(tmpdir) - 1; i > 0 && tmpdir[i] == '/'; i--) + ; + i++; + if (asprintf(&TMPOUTNAME, "%.*s/patchoXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPOUTNAME)) < 0) + pfatal("can't create %s", TMPOUTNAME); + close(fd); + + if (asprintf(&TMPINNAME, "%.*s/patchiXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPINNAME)) < 0) + pfatal("can't create %s", TMPINNAME); + close(fd); + + if (asprintf(&TMPREJNAME, "%.*s/patchrXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPREJNAME)) < 0) + pfatal("can't create %s", TMPREJNAME); + close(fd); + + if (asprintf(&TMPPATNAME, "%.*s/patchpXXXXXXXXXX", i, tmpdir) == -1) + fatal("cannot allocate memory"); + if ((fd = mkstemp(TMPPATNAME)) < 0) + pfatal("can't create %s", TMPPATNAME); + close(fd); + + v = getenv("SIMPLE_BACKUP_SUFFIX"); + if (v) + simple_backup_suffix = v; + else + simple_backup_suffix = ORIGEXT; + + /* parse switches */ + Argc = argc; + Argv = argv; + get_some_switches(); + + if (backup_type == none) { + if ((v = getenv("PATCH_VERSION_CONTROL")) == NULL) + v = getenv("VERSION_CONTROL"); + if (v != NULL || !posix) + backup_type = get_version(v); /* OK to pass NULL. */ + } + + /* make sure we clean up /tmp in case of disaster */ + set_signals(0); + + for (open_patch_file(filearg[1]); there_is_another_patch(); + reinitialize_almost_everything()) { + /* for each patch in patch file */ + + warn_on_invalid_line = true; + + if (outname == NULL) + outname = savestr(filearg[0]); + + /* for ed script just up and do it and exit */ + if (diff_type == ED_DIFF) { + do_ed_script(); + continue; + } + /* initialize the patched file */ + if (!skip_rest_of_patch) + init_output(TMPOUTNAME); + + /* initialize reject file */ + init_reject(TMPREJNAME); + + /* find out where all the lines are */ + if (!skip_rest_of_patch) + scan_input(filearg[0]); + + /* from here on, open no standard i/o files, because malloc */ + /* might misfire and we can't catch it easily */ + + /* apply each hunk of patch */ + hunk = 0; + failed = 0; + out_of_mem = false; + while (another_hunk()) { + hunk++; + fuzz = 0; + mymaxfuzz = pch_context(); + if (maxfuzz < mymaxfuzz) + mymaxfuzz = maxfuzz; + if (!skip_rest_of_patch) { + do { + where = locate_hunk(fuzz); + if (hunk == 1 && where == 0 && !force) { + /* dwim for reversed patch? */ + if (!pch_swap()) { + if (fuzz == 0) + say("Not enough memory to try swapped hunk! Assuming unswapped.\n"); + continue; + } + reverse = !reverse; + /* try again */ + where = locate_hunk(fuzz); + if (where == 0) { + /* didn't find it swapped */ + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + reverse = !reverse; + } else if (noreverse) { + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + reverse = !reverse; + say("Ignoring previously applied (or reversed) patch.\n"); + skip_rest_of_patch = true; + } else if (batch) { + if (verbose) + say("%seversed (or previously applied) patch detected! %s -R.", + reverse ? "R" : "Unr", + reverse ? "Assuming" : "Ignoring"); + } else { + ask("%seversed (or previously applied) patch detected! %s -R? [y] ", + reverse ? "R" : "Unr", + reverse ? "Assume" : "Ignore"); + if (*buf == 'n') { + ask("Apply anyway? [n] "); + if (*buf != 'y') + skip_rest_of_patch = true; + where = 0; + reverse = !reverse; + if (!pch_swap()) + /* put it back to normal */ + fatal("lost hunk on alloc error!\n"); + } + } + } + } while (!skip_rest_of_patch && where == 0 && + ++fuzz <= mymaxfuzz); + + if (skip_rest_of_patch) { /* just got decided */ + if (ferror(ofp) || fclose(ofp)) { + say("Error writing %s\n", + TMPOUTNAME); + error = 1; + } + ofp = NULL; + } + } + newwhere = pch_newfirst() + last_offset; + if (skip_rest_of_patch) { + abort_hunk(); + failed++; + if (verbose) + say("Hunk #%d ignored at %ld.\n", + hunk, newwhere); + } else if (where == 0) { + abort_hunk(); + failed++; + if (verbose) + say("Hunk #%d failed at %ld.\n", + hunk, newwhere); + } else { + apply_hunk(where); + if (verbose) { + say("Hunk #%d succeeded at %ld", + hunk, newwhere); + if (fuzz != 0) + say(" with fuzz %ld", fuzz); + if (last_offset) + say(" (offset %ld line%s)", + last_offset, + last_offset == 1L ? "" : "s"); + say(".\n"); + } + } + } + + if (out_of_mem && using_plan_a) { + Argc = Argc_last; + Argv = Argv_last; + say("\n\nRan out of memory using Plan A--trying again...\n\n"); + if (ofp) + fclose(ofp); + ofp = NULL; + if (rejfp) + fclose(rejfp); + rejfp = NULL; + continue; + } + if (hunk == 0) + fatal("Internal error: hunk should not be 0\n"); + + /* finish spewing out the new file */ + if (!skip_rest_of_patch && !spew_output()) { + say("Can't write %s\n", TMPOUTNAME); + error = 1; + } + + /* and put the output where desired */ + ignore_signals(); + if (!skip_rest_of_patch) { + struct stat statbuf; + char *realout = outname; + + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + realout = TMPOUTNAME; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + + if (remove_empty_files && + stat(realout, &statbuf) == 0 && + statbuf.st_size == 0) { + if (verbose) + say("Removing %s (empty after patching).\n", + realout); + unlink(realout); + } + } + } + if (ferror(rejfp) || fclose(rejfp)) { + say("Error writing %s\n", rejname); + error = 1; + } + rejfp = NULL; + if (failed) { + error = 1; + if (*rejname == '\0') { + if (strlcpy(rejname, outname, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + if (strlcat(rejname, REJEXT, + sizeof(rejname)) >= sizeof(rejname)) + fatal("filename %s is too long\n", outname); + } + if (skip_rest_of_patch) { + say("%d out of %d hunks ignored--saving rejects to %s\n", + failed, hunk, rejname); + } else { + say("%d out of %d hunks failed--saving rejects to %s\n", + failed, hunk, rejname); + } + if (!check_only && move_file(TMPREJNAME, rejname) < 0) + trejkeep = true; + } + set_signals(1); + } + my_exit(error); + /* NOTREACHED */ +} + +/* Prepare to find the next patch to do in the patch file. */ + +static void +reinitialize_almost_everything(void) +{ + re_patch(); + re_input(); + + input_lines = 0; + last_frozen_line = 0; + + filec = 0; + if (!out_of_mem) { + free(filearg[0]); + filearg[0] = NULL; + } + + free(outname); + outname = NULL; + + last_offset = 0; + diff_type = 0; + + free(revision); + revision = NULL; + + reverse = reverse_flag_specified; + skip_rest_of_patch = false; + + get_some_switches(); +} + +/* Process switches and filenames. */ + +static void +get_some_switches(void) +{ + const char *options = "b::B:cCd:D:eEfF:i:lnNo:p:r:RstuvV:x:z:"; + static struct option longopts[] = { + {"backup", no_argument, 0, 'b'}, + {"batch", no_argument, 0, 't'}, + {"check", no_argument, 0, 'C'}, + {"context", no_argument, 0, 'c'}, + {"debug", required_argument, 0, 'x'}, + {"directory", required_argument, 0, 'd'}, + {"ed", no_argument, 0, 'e'}, + {"force", no_argument, 0, 'f'}, + {"forward", no_argument, 0, 'N'}, + {"fuzz", required_argument, 0, 'F'}, + {"ifdef", required_argument, 0, 'D'}, + {"input", required_argument, 0, 'i'}, + {"ignore-whitespace", no_argument, 0, 'l'}, + {"normal", no_argument, 0, 'n'}, + {"output", required_argument, 0, 'o'}, + {"prefix", required_argument, 0, 'B'}, + {"quiet", no_argument, 0, 's'}, + {"reject-file", required_argument, 0, 'r'}, + {"remove-empty-files", no_argument, 0, 'E'}, + {"reverse", no_argument, 0, 'R'}, + {"silent", no_argument, 0, 's'}, + {"strip", required_argument, 0, 'p'}, + {"suffix", required_argument, 0, 'z'}, + {"unified", no_argument, 0, 'u'}, + {"version", no_argument, 0, 'v'}, + {"version-control", required_argument, 0, 'V'}, + {"posix", no_argument, &posix, 1}, + {NULL, 0, 0, 0} + }; + int ch; + + rejname[0] = '\0'; + Argc_last = Argc; + Argv_last = Argv; + if (!Argc) + return; + optreset = optind = 1; + while ((ch = getopt_long(Argc, Argv, options, longopts, NULL)) != -1) { + switch (ch) { + case 'b': + if (backup_type == none) + backup_type = numbered_existing; + if (optarg == NULL) + break; + if (verbose) + say("Warning, the ``-b suffix'' option has been" + " obsoleted by the -z option.\n"); + /* FALLTHROUGH */ + case 'z': + /* must directly follow 'b' case for backwards compat */ + simple_backup_suffix = savestr(optarg); + break; + case 'B': + origprae = savestr(optarg); + break; + case 'c': + diff_type = CONTEXT_DIFF; + break; + case 'C': + check_only = true; + break; + case 'd': + if (chdir(optarg) < 0) + pfatal("can't cd to %s", optarg); + break; + case 'D': + do_defines = true; + if (!isalpha((unsigned char)*optarg) && *optarg != '_') + fatal("argument to -D is not an identifier\n"); + snprintf(if_defined, sizeof if_defined, + "#ifdef %s\n", optarg); + snprintf(not_defined, sizeof not_defined, + "#ifndef %s\n", optarg); + snprintf(end_defined, sizeof end_defined, + "#endif /* %s */\n", optarg); + break; + case 'e': + diff_type = ED_DIFF; + break; + case 'E': + remove_empty_files = true; + break; + case 'f': + force = true; + break; + case 'F': + maxfuzz = atoi(optarg); + break; + case 'i': + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(optarg); + break; + case 'l': + canonicalize = true; + break; + case 'n': + diff_type = NORMAL_DIFF; + break; + case 'N': + noreverse = true; + break; + case 'o': + outname = savestr(optarg); + break; + case 'p': + strippath = atoi(optarg); + break; + case 'r': + if (strlcpy(rejname, optarg, + sizeof(rejname)) >= sizeof(rejname)) + fatal("argument for -r is too long\n"); + break; + case 'R': + reverse = true; + reverse_flag_specified = true; + break; + case 's': + verbose = false; + break; + case 't': + batch = true; + break; + case 'u': + diff_type = UNI_DIFF; + break; + case 'v': + version(); + break; + case 'V': + backup_type = get_version(optarg); + break; +#ifdef DEBUGGING + case 'x': + debug = atoi(optarg); + break; +#endif + default: + if (ch != '\0') + usage(); + break; + } + } + Argc -= optind; + Argv += optind; + + if (Argc > 0) { + filearg[0] = savestr(*Argv++); + Argc--; + while (Argc > 0) { + if (++filec == MAXFILEC) + fatal("too many file arguments\n"); + filearg[filec] = savestr(*Argv++); + Argc--; + } + } + + if (getenv("POSIXLY_CORRECT") != NULL) + posix = 1; +} + +static void +usage(void) +{ + fprintf(stderr, +"usage: patch [-bCcEeflNnRstuv] [-B backup-prefix] [-D symbol] [-d directory]\n" +" [-F max-fuzz] [-i patchfile] [-o out-file] [-p strip-count]\n" +" [-r rej-name] [-V t | nil | never] [-x number] [-z backup-ext]\n" +" [--posix] [origfile [patchfile]]\n" +" patch = first_guess) /* do not try lines < 0 */ + max_neg_offset = first_guess - 1; + if (first_guess <= input_lines && patch_match(first_guess, 0, fuzz)) + return first_guess; + for (offset = 1; ; offset++) { + bool check_after = (offset <= max_pos_offset); + bool check_before = (offset <= max_neg_offset); + + if (check_after && patch_match(first_guess, offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, offset); +#endif + last_offset = offset; + return first_guess + offset; + } else if (check_before && patch_match(first_guess, -offset, fuzz)) { +#ifdef DEBUGGING + if (debug & 1) + say("Offset changing from %ld to %ld\n", + last_offset, -offset); +#endif + last_offset = -offset; + return first_guess - offset; + } else if (!check_before && !check_after) + return 0; + } +} + +/* We did not find the pattern, dump out the hunk so they can handle it. */ + +static void +abort_context_hunk(void) +{ + LINENUM i; + const LINENUM pat_end = pch_end(); + /* + * add in last_offset to guess the same as the previous successful + * hunk + */ + const LINENUM oldfirst = pch_first() + last_offset; + const LINENUM newfirst = pch_newfirst() + last_offset; + const LINENUM oldlast = oldfirst + pch_ptrn_lines() - 1; + const LINENUM newlast = newfirst + pch_repl_lines() - 1; + const char *stars = (diff_type >= NEW_CONTEXT_DIFF ? " ****" : ""); + const char *minuses = (diff_type >= NEW_CONTEXT_DIFF ? " ----" : " -----"); + + fprintf(rejfp, "***************\n"); + for (i = 0; i <= pat_end; i++) { + switch (pch_char(i)) { + case '*': + if (oldlast < oldfirst) + fprintf(rejfp, "*** 0%s\n", stars); + else if (oldlast == oldfirst) + fprintf(rejfp, "*** %ld%s\n", oldfirst, stars); + else + fprintf(rejfp, "*** %ld,%ld%s\n", oldfirst, + oldlast, stars); + break; + case '=': + if (newlast < newfirst) + fprintf(rejfp, "--- 0%s\n", minuses); + else if (newlast == newfirst) + fprintf(rejfp, "--- %ld%s\n", newfirst, minuses); + else + fprintf(rejfp, "--- %ld,%ld%s\n", newfirst, + newlast, minuses); + break; + case '\n': + fprintf(rejfp, "%s", pfetch(i)); + break; + case ' ': + case '-': + case '+': + case '!': + fprintf(rejfp, "%c %s", pch_char(i), pfetch(i)); + break; + default: + fatal("fatal internal error in abort_context_hunk\n"); + } + } +} + +static void +rej_line(int ch, LINENUM i) +{ + size_t len; + const char *line = pfetch(i); + + len = strlen(line); + + fprintf(rejfp, "%c%s", ch, line); + if (len == 0 || line[len-1] != '\n') + fprintf(rejfp, "\n\\ No newline at end of file\n"); +} + +static void +abort_hunk(void) +{ + LINENUM i, j, split; + int ch1, ch2; + const LINENUM pat_end = pch_end(); + const LINENUM oldfirst = pch_first() + last_offset; + const LINENUM newfirst = pch_newfirst() + last_offset; + + if (diff_type != UNI_DIFF) { + abort_context_hunk(); + return; + } + split = -1; + for (i = 0; i <= pat_end; i++) { + if (pch_char(i) == '=') { + split = i; + break; + } + } + if (split == -1) { + fprintf(rejfp, "malformed hunk: no split found\n"); + return; + } + i = 0; + j = split + 1; + fprintf(rejfp, "@@ -%ld,%ld +%ld,%ld @@\n", + pch_ptrn_lines() ? oldfirst : 0, + pch_ptrn_lines(), newfirst, pch_repl_lines()); + while (i < split || j <= pat_end) { + ch1 = i < split ? pch_char(i) : -1; + ch2 = j <= pat_end ? pch_char(j) : -1; + if (ch1 == '-') { + rej_line('-', i); + i++; + } else if (ch1 == ' ' && ch2 == ' ') { + rej_line(' ', i); + i++; + j++; + } else if (ch1 == '!' && ch2 == '!') { + while (i < split && ch1 == '!') { + rej_line('-', i); + i++; + ch1 = i < split ? pch_char(i) : -1; + } + while (j <= pat_end && ch2 == '!') { + rej_line('+', j); + j++; + ch2 = j <= pat_end ? pch_char(j) : -1; + } + } else if (ch1 == '*') { + i++; + } else if (ch2 == '+' || ch2 == ' ') { + rej_line(ch2, j); + j++; + } else { + fprintf(rejfp, "internal error on (%ld %ld %ld)\n", + i, split, j); + rej_line(ch1, i); + rej_line(ch2, j); + return; + } + } +} + +/* We found where to apply it (we hope), so do it. */ + +static void +apply_hunk(LINENUM where) +{ + LINENUM old = 1; + const LINENUM lastline = pch_ptrn_lines(); + LINENUM new = lastline + 1; +#define OUTSIDE 0 +#define IN_IFNDEF 1 +#define IN_IFDEF 2 +#define IN_ELSE 3 + int def_state = OUTSIDE; + const LINENUM pat_end = pch_end(); + + where--; + while (pch_char(new) == '=' || pch_char(new) == '\n') + new++; + + while (old <= lastline) { + if (pch_char(old) == '-') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == OUTSIDE) { + fputs(not_defined, ofp); + def_state = IN_IFNDEF; + } else if (def_state == IN_IFDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + fputs(pfetch(old), ofp); + } + last_frozen_line++; + old++; + } else if (new > pat_end) { + break; + } else if (pch_char(new) == '+') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == IN_IFNDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } else if (def_state == OUTSIDE) { + fputs(if_defined, ofp); + def_state = IN_IFDEF; + } + } + fputs(pfetch(new), ofp); + new++; + } else if (pch_char(new) != pch_char(old)) { + say("Out-of-sync patch, lines %ld,%ld--mangled text or line numbers, maybe?\n", + pch_hunk_beg() + old, + pch_hunk_beg() + new); +#ifdef DEBUGGING + say("oldchar = '%c', newchar = '%c'\n", + pch_char(old), pch_char(new)); +#endif + my_exit(2); + } else if (pch_char(new) == '!') { + copy_till(where + old - 1, false); + if (do_defines) { + fputs(not_defined, ofp); + def_state = IN_IFNDEF; + } + while (pch_char(old) == '!') { + if (do_defines) { + fputs(pfetch(old), ofp); + } + last_frozen_line++; + old++; + } + if (do_defines) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + while (pch_char(new) == '!') { + fputs(pfetch(new), ofp); + new++; + } + } else { + if (pch_char(new) != ' ') + fatal("Internal error: expected ' '\n"); + old++; + new++; + if (do_defines && def_state != OUTSIDE) { + fputs(end_defined, ofp); + def_state = OUTSIDE; + } + } + } + if (new <= pat_end && pch_char(new) == '+') { + copy_till(where + old - 1, false); + if (do_defines) { + if (def_state == OUTSIDE) { + fputs(if_defined, ofp); + def_state = IN_IFDEF; + } else if (def_state == IN_IFNDEF) { + fputs(else_defined, ofp); + def_state = IN_ELSE; + } + } + while (new <= pat_end && pch_char(new) == '+') { + fputs(pfetch(new), ofp); + new++; + } + } + if (do_defines && def_state != OUTSIDE) { + fputs(end_defined, ofp); + } +} + +/* + * Open the new file. + */ +static void +init_output(const char *name) +{ + ofp = fopen(name, "w"); + if (ofp == NULL) + pfatal("can't create %s", name); +} + +/* + * Open a file to put hunks we can't locate. + */ +static void +init_reject(const char *name) +{ + rejfp = fopen(name, "w"); + if (rejfp == NULL) + pfatal("can't create %s", name); +} + +/* + * Copy input file to output, up to wherever hunk is to be applied. + * If endoffile is true, treat the last line specially since it may + * lack a newline. + */ +static void +copy_till(LINENUM lastline, bool endoffile) +{ + if (last_frozen_line > lastline) + fatal("misordered hunks! output would be garbled\n"); + while (last_frozen_line < lastline) { + if (++last_frozen_line == lastline && endoffile) + dump_line(last_frozen_line, !last_line_missing_eol); + else + dump_line(last_frozen_line, true); + } +} + +/* + * Finish copying the input file to the output file. + */ +static bool +spew_output(void) +{ + int rv; + +#ifdef DEBUGGING + if (debug & 256) + say("il=%ld lfl=%ld\n", input_lines, last_frozen_line); +#endif + if (input_lines) + copy_till(input_lines, true); /* dump remainder of file */ + rv = ferror(ofp) == 0 && fclose(ofp) == 0; + ofp = NULL; + return rv; +} + +/* + * Copy one line from input to output. + */ +static void +dump_line(LINENUM line, bool write_newline) +{ + char *s; + + s = ifetch(line, 0); + if (s == NULL) + return; + /* Note: string is not NUL terminated. */ + for (; *s != '\n'; s++) + putc(*s, ofp); + if (write_newline) + putc('\n', ofp); +} + +/* + * Does the patch pattern match at line base+offset? + */ +static bool +patch_match(LINENUM base, LINENUM offset, LINENUM fuzz) +{ + LINENUM pline = 1 + fuzz; + LINENUM iline; + LINENUM pat_lines = pch_ptrn_lines() - fuzz; + const char *ilineptr; + const char *plineptr; + short plinelen; + + for (iline = base + offset + fuzz; pline <= pat_lines; pline++, iline++) { + ilineptr = ifetch(iline, offset >= 0); + if (ilineptr == NULL) + return false; + plineptr = pfetch(pline); + plinelen = pch_line_len(pline); + if (canonicalize) { + if (!similar(ilineptr, plineptr, plinelen)) + return false; + } else if (strnNE(ilineptr, plineptr, plinelen)) + return false; + if (iline == input_lines) { + /* + * We are looking at the last line of the file. + * If the file has no eol, the patch line should + * not have one either and vice-versa. Note that + * plinelen > 0. + */ + if (last_line_missing_eol) { + if (plineptr[plinelen - 1] == '\n') + return false; + } else { + if (plineptr[plinelen - 1] != '\n') + return false; + } + } + } + return true; +} + +/* + * Do two lines match with canonicalized white space? + */ +static bool +similar(const char *a, const char *b, int len) +{ + while (len) { + if (isspace((unsigned char)*b)) { /* whitespace (or \n) to match? */ + if (!isspace((unsigned char)*a)) /* no corresponding whitespace? */ + return false; + while (len && isspace((unsigned char)*b) && *b != '\n') + b++, len--; /* skip pattern whitespace */ + while (isspace((unsigned char)*a) && *a != '\n') + a++; /* skip target whitespace */ + if (*a == '\n' || *b == '\n') + return (*a == *b); /* should end in sync */ + } else if (*a++ != *b++) /* match non-whitespace chars */ + return false; + else + len--; /* probably not necessary */ + } + return true; /* actually, this is not reached */ + /* since there is always a \n */ +} diff --git a/usr.bin/patch/pathnames.h b/usr.bin/patch/pathnames.h new file mode 100644 index 0000000..e9d3ccb --- /dev/null +++ b/usr.bin/patch/pathnames.h @@ -0,0 +1,14 @@ +/* + * $OpenBSD: pathnames.h,v 1.1 2003/07/29 20:10:17 millert Exp $ + * $DragonFly: src/usr.bin/patch/pathnames.h,v 1.2 2008/08/11 00:04:12 joerg Exp $ + * $NetBSD: pathnames.h,v 1.1 2008/09/19 18:33:34 joerg Exp $ + */ + +/* + * Placed in the public domain by Todd C. Miller + * on July 29, 2003. + */ + +#include + +#define _PATH_ED "/bin/ed" diff --git a/usr.bin/patch/pch.c b/usr.bin/patch/pch.c new file mode 100644 index 0000000..8a635ad --- /dev/null +++ b/usr.bin/patch/pch.c @@ -0,0 +1,1600 @@ +/* + * $OpenBSD: pch.c,v 1.37 2007/09/02 15:19:33 deraadt Exp $ + * $DragonFly: src/usr.bin/patch/pch.c,v 1.6 2008/08/10 23:35:40 joerg Exp $ + * $NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#include +__RCSID("$NetBSD: pch.c,v 1.30 2018/06/18 18:33:31 christos Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "pch.h" +#include "pathnames.h" + +/* Patch (diff listing) abstract type. */ + +static long p_filesize; /* size of the patch file */ +static LINENUM p_first; /* 1st line number */ +static LINENUM p_newfirst; /* 1st line number of replacement */ +static LINENUM p_ptrn_lines; /* # lines in pattern */ +static LINENUM p_repl_lines; /* # lines in replacement text */ +static LINENUM p_end = -1; /* last line in hunk */ +static LINENUM p_max; /* max allowed value of p_end */ +static LINENUM p_context = 3; /* # of context lines */ +static LINENUM p_input_line = 0; /* current line # from patch file */ +static char **p_line = NULL;/* the text of the hunk */ +static short *p_len = NULL; /* length of each line */ +static char *p_char = NULL; /* +, -, and ! */ +static int hunkmax = INITHUNKMAX; /* size of above arrays to begin with */ +static int p_indent; /* indent to patch */ +static LINENUM p_base; /* where to intuit this time */ +static LINENUM p_bline; /* line # of p_base */ +static LINENUM p_start; /* where intuit found a patch */ +static LINENUM p_sline; /* and the line number for it */ +static LINENUM p_hunk_beg; /* line number of current hunk */ +static LINENUM p_efake = -1; /* end of faked up lines--don't free */ +static LINENUM p_bfake = -1; /* beg of faked up lines */ +static FILE *pfp = NULL; /* patch file pointer */ +static char *bestguess = NULL; /* guess at correct filename */ + +static void grow_hunkmax(void); +static int intuit_diff_type(void); +static void next_intuit_at(LINENUM, LINENUM); +static void skip_to(LINENUM, LINENUM); +static char *pgets(char *, int, FILE *); +static char *best_name(const struct file_name *, bool); +static char *posix_name(const struct file_name *, bool); +static size_t num_components(const char *); + +/* + * Prepare to look for the next patch in the patch file. + */ +void +re_patch(void) +{ + p_first = 0; + p_newfirst = 0; + p_ptrn_lines = 0; + p_repl_lines = 0; + p_end = (LINENUM) - 1; + p_max = 0; + p_indent = 0; +} + +/* + * Open the patch file at the beginning of time. + */ +void +open_patch_file(const char *filename) +{ + struct stat filestat; + + if (filename == NULL || *filename == '\0' || strEQ(filename, "-")) { + pfp = fopen(TMPPATNAME, "w"); + if (pfp == NULL) + pfatal("can't create %s", TMPPATNAME); + while (fgets(buf, buf_len, stdin) != NULL) + fputs(buf, pfp); + if (ferror(pfp) || fclose(pfp)) + pfatal("can't write %s", TMPPATNAME); + filename = TMPPATNAME; + } + pfp = fopen(filename, "r"); + if (pfp == NULL) + pfatal("patch file %s not found", filename); + fstat(fileno(pfp), &filestat); + p_filesize = filestat.st_size; + next_intuit_at(0L, 1L); /* start at the beginning */ + set_hunkmax(); +} + +/* + * Make sure our dynamically realloced tables are malloced to begin with. + */ +void +set_hunkmax(void) +{ + if (p_line == NULL) + p_line = calloc((size_t) hunkmax, sizeof(char *)); + if (p_len == NULL) + p_len = calloc((size_t) hunkmax, sizeof(short)); + if (p_char == NULL) + p_char = calloc((size_t) hunkmax, sizeof(char)); +} + +/* + * Enlarge the arrays containing the current hunk of patch. + */ +static void +grow_hunkmax(void) +{ + int new_hunkmax; + char **new_p_line; + short *new_p_len; + char *new_p_char; + + new_hunkmax = hunkmax * 2; + + if (p_line == NULL || p_len == NULL || p_char == NULL) + fatal("Internal memory allocation error\n"); + + new_p_line = pch_realloc(p_line, new_hunkmax, sizeof(char *)); + if (new_p_line == NULL) + free(p_line); + + new_p_len = pch_realloc(p_len, new_hunkmax, sizeof(short)); + if (new_p_len == NULL) + free(p_len); + + new_p_char = pch_realloc(p_char, new_hunkmax, sizeof(char)); + if (new_p_char == NULL) + free(p_char); + + p_char = new_p_char; + p_len = new_p_len; + p_line = new_p_line; + + if (p_line != NULL && p_len != NULL && p_char != NULL) { + hunkmax = new_hunkmax; + return; + } + + if (!using_plan_a) + fatal("out of memory\n"); + out_of_mem = true; /* whatever is null will be allocated again */ + /* from within plan_a(), of all places */ +} + +/* True if the remainder of the patch file contains a diff of some sort. */ + +bool +there_is_another_patch(void) +{ + bool exists = false; + + if (p_base != 0L && p_base >= p_filesize) { + if (verbose) + say("done\n"); + return false; + } + if (verbose) + say("Hmm..."); + diff_type = intuit_diff_type(); + if (!diff_type) { + if (p_base != 0L) { + if (verbose) + say(" Ignoring the trailing garbage.\ndone\n"); + } else + say(" I can't seem to find a patch in there anywhere.\n"); + return false; + } + if (verbose) + say(" %sooks like %s to me...\n", + (p_base == 0L ? "L" : "The next patch l"), + diff_type == UNI_DIFF ? "a unified diff" : + diff_type == CONTEXT_DIFF ? "a context diff" : + diff_type == NEW_CONTEXT_DIFF ? "a new-style context diff" : + diff_type == NORMAL_DIFF ? "a normal diff" : + "an ed script"); + if (p_indent && verbose) + say("(Patch is indented %d space%s.)\n", p_indent, + p_indent == 1 ? "" : "s"); + skip_to(p_start, p_sline); + while (filearg[0] == NULL) { + if (force || batch) { + say("No file to patch. Skipping...\n"); + filearg[0] = savestr(bestguess); + skip_rest_of_patch = true; + return true; + } + ask("File to patch: "); + if (*buf != '\n') { + free(bestguess); + bestguess = savestr(buf); + filearg[0] = fetchname(buf, &exists, 0); + } + if (!exists) { + ask("No file found--skip this patch? [n] "); + if (*buf != 'y') + continue; + if (verbose) + say("Skipping patch...\n"); + free(filearg[0]); + filearg[0] = fetchname(bestguess, &exists, 0); + skip_rest_of_patch = true; + return true; + } + } + return true; +} + +/* Determine what kind of diff is in the remaining part of the patch file. */ + +static int +intuit_diff_type(void) +{ + long this_line = 0, previous_line; + long first_command_line = -1; + LINENUM fcl_line = -1; + bool last_line_was_command = false, this_is_a_command = false; + bool stars_last_line = false, stars_this_line = false; + char *s, *t; + int indent, retval; + struct file_name names[MAX_FILE]; + + memset(names, 0, sizeof(names)); + ok_to_create_file = false; + fseek(pfp, p_base, SEEK_SET); + p_input_line = p_bline - 1; + for (;;) { + previous_line = this_line; + last_line_was_command = this_is_a_command; + stars_last_line = stars_this_line; + this_line = ftell(pfp); + indent = 0; + p_input_line++; + if (fgets(buf, buf_len, pfp) == NULL) { + if (first_command_line >= 0L) { + /* nothing but deletes!? */ + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } else { + p_start = this_line; + p_sline = p_input_line; + retval = 0; + goto scan_exit; + } + } + for (s = buf; *s == ' ' || *s == '\t' || *s == 'X'; s++) { + if (*s == '\t') + indent += 8 - (indent % 8); + else + indent++; + } + for (t = s; isdigit((unsigned char)*t) || *t == ','; t++) + ; + this_is_a_command = (isdigit((unsigned char)*s) && + (*t == 'd' || *t == 'c' || *t == 'a')); + if (first_command_line < 0L && this_is_a_command) { + first_command_line = this_line; + fcl_line = p_input_line; + p_indent = indent; /* assume this for now */ + } + if (!stars_last_line && strnEQ(s, "*** ", 4)) + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "--- ", 4)) + names[NEW_FILE].path = fetchname(s + 4, + &names[NEW_FILE].exists, strippath); + else if (strnEQ(s, "+++ ", 4)) + /* pretend it is the old name */ + names[OLD_FILE].path = fetchname(s + 4, + &names[OLD_FILE].exists, strippath); + else if (strnEQ(s, "Index:", 6)) + names[INDEX_FILE].path = fetchname(s + 6, + &names[INDEX_FILE].exists, strippath); + else if (strnEQ(s, "Prereq:", 7)) { + for (t = s + 7; isspace((unsigned char)*t); t++) + ; + revision = savestr(t); + for (t = revision; *t && !isspace((unsigned char)*t); t++) + ; + *t = '\0'; + if (*revision == '\0') { + free(revision); + revision = NULL; + } + } + if ((!diff_type || diff_type == ED_DIFF) && + first_command_line >= 0L && + strEQ(s, ".\n")) { + p_indent = indent; + p_start = first_command_line; + p_sline = fcl_line; + retval = ED_DIFF; + goto scan_exit; + } + if ((!diff_type || diff_type == UNI_DIFF) && strnEQ(s, "@@ -", 4)) { + if (strnEQ(s + 4, "0,0", 3)) + ok_to_create_file = true; + p_indent = indent; + p_start = this_line; + p_sline = p_input_line; + retval = UNI_DIFF; + goto scan_exit; + } + stars_this_line = strnEQ(s, "********", 8); + if ((!diff_type || diff_type == CONTEXT_DIFF) && stars_last_line && + strnEQ(s, "*** ", 4)) { + if (atol(s + 4) == 0) + ok_to_create_file = true; + /* + * If this is a new context diff the character just + * before the newline is a '*'. + */ + while (*s != '\n') + s++; + p_indent = indent; + p_start = previous_line; + p_sline = p_input_line - 1; + retval = (*(s - 1) == '*' ? NEW_CONTEXT_DIFF : CONTEXT_DIFF); + goto scan_exit; + } + if ((!diff_type || diff_type == NORMAL_DIFF) && + last_line_was_command && + (strnEQ(s, "< ", 2) || strnEQ(s, "> ", 2))) { + p_start = previous_line; + p_sline = p_input_line - 1; + p_indent = indent; + retval = NORMAL_DIFF; + goto scan_exit; + } + } +scan_exit: + if (retval == UNI_DIFF) { + /* unswap old and new */ + struct file_name tmp = names[OLD_FILE]; + names[OLD_FILE] = names[NEW_FILE]; + names[NEW_FILE] = tmp; + } + if (filearg[0] == NULL) { + if (posix) + filearg[0] = posix_name(names, ok_to_create_file); + else { + /* Ignore the Index: name for context diffs, like GNU */ + if (names[OLD_FILE].path != NULL || + names[NEW_FILE].path != NULL) { + free(names[INDEX_FILE].path); + names[INDEX_FILE].path = NULL; + } + filearg[0] = best_name(names, ok_to_create_file); + } + } + + free(bestguess); + bestguess = NULL; + if (filearg[0] != NULL) + bestguess = savestr(filearg[0]); + else if (!ok_to_create_file) { + /* + * We don't want to create a new file but we need a + * filename to set bestguess. Avoid setting filearg[0] + * so the file is not created automatically. + */ + if (posix) + bestguess = posix_name(names, true); + else + bestguess = best_name(names, true); + } + free(names[OLD_FILE].path); + free(names[NEW_FILE].path); + free(names[INDEX_FILE].path); + return retval; +} + +/* + * Remember where this patch ends so we know where to start up again. + */ +static void +next_intuit_at(LINENUM file_pos, LINENUM file_line) +{ + p_base = file_pos; + p_bline = file_line; +} + +/* + * Basically a verbose fseek() to the actual diff listing. + */ +static void +skip_to(LINENUM file_pos, LINENUM file_line) +{ + char *ret; + + if (p_base > file_pos) + fatal("Internal error: seek %ld>%ld\n", p_base, file_pos); + if (verbose && p_base < file_pos) { + fseek(pfp, p_base, SEEK_SET); + say("The text leading up to this was:\n--------------------------\n"); + while (ftell(pfp) < file_pos) { + ret = fgets(buf, buf_len, pfp); + if (ret == NULL) + fatal("Unexpected end of file\n"); + say("|%s", buf); + } + say("--------------------------\n"); + } else + fseek(pfp, file_pos, SEEK_SET); + p_input_line = file_line - 1; +} + +/* Make this a function for better debugging. */ +__dead static void +malformed(void) +{ + fatal("malformed patch at line %ld: %s", p_input_line, buf); + /* about as informative as "Syntax error" in C */ +} + +static LINENUM +getlinenum(const char *s) +{ + LINENUM l = (LINENUM)atol(s); + if (l < 0) { + l = 0; + malformed(); + } + return l; +} + +static LINENUM +getskiplinenum(char **p) +{ + char *s = *p; + LINENUM l = getlinenum(s); + while (isdigit((unsigned char)*s)) + s++; + *p = s; + return l; +} + +/* + * True if the line has been discarded (i.e., it is a line saying + * "\ No newline at end of file".) + */ +static bool +remove_special_line(void) +{ + int c; + + c = fgetc(pfp); + if (c == '\\') { + do { + c = fgetc(pfp); + } while (c != EOF && c != '\n'); + + return true; + } + if (c != EOF) + fseek(pfp, -1L, SEEK_CUR); + + return false; +} + +/* + * True if there is more of the current diff listing to process. + */ +bool +another_hunk(void) +{ + long line_beginning; /* file pos of the current line */ + LINENUM repl_beginning; /* index of --- line */ + LINENUM fillcnt; /* #lines of missing ptrn or repl */ + LINENUM fillsrc; /* index of first line to copy */ + LINENUM filldst; /* index of first missing line */ + bool ptrn_spaces_eaten; /* ptrn was slightly misformed */ + bool repl_could_be_missing; /* no + or ! lines in this hunk */ + bool repl_missing; /* we are now backtracking */ + long repl_backtrack_position; /* file pos of first repl line */ + LINENUM repl_patch_line; /* input line number for same */ + LINENUM ptrn_copiable; /* # of copiable lines in ptrn */ + char *s, *ret; + int context = 0; + + while (p_end >= 0) { + if (p_end == p_efake) + p_end = p_bfake; /* don't free twice */ + else + free(p_line[p_end]); + p_end--; + } + p_efake = -1; + + p_max = hunkmax; /* gets reduced when --- found */ + if (diff_type == CONTEXT_DIFF || diff_type == NEW_CONTEXT_DIFF) { + line_beginning = ftell(pfp); + repl_beginning = 0; + fillcnt = 0; + fillsrc = 0; + filldst = 0; + ptrn_spaces_eaten = false; + repl_could_be_missing = true; + repl_missing = false; + repl_backtrack_position = 0; + repl_patch_line = 0; + ptrn_copiable = 0; + + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL || strnNE(buf, "********", 8)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + p_context = 100; + p_hunk_beg = p_input_line + 1; + while (p_end < p_max) { + line_beginning = ftell(pfp); + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL) { + if (p_max - p_end < 4) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", buf_len); + } else { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected end of file in patch\n"); + } + } + p_end++; + if (p_end >= hunkmax) + fatal("Internal error: hunk larger than hunk " + "buffer size"); + p_char[p_end] = *buf; + p_line[p_end] = NULL; + switch (*buf) { + case '*': + if (strnEQ(buf, "********", 8)) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } else + fatal("unexpected end of hunk " + "at line %ld\n", + p_input_line); + } + if (p_end != 0) { + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("unexpected *** at line %ld: %s", + p_input_line, buf); + } + context = 0; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + for (s = buf; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + if (strnEQ(s, "0,0", 3)) + memmove(s, s + 2, strlen(s + 2) + 1); + p_first = getskiplinenum(&s); + if (*s == ',') { + for (; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_ptrn_lines = (getlinenum(s)) - p_first + 1; + if (p_ptrn_lines < 0) + malformed(); + } else if (p_first) + p_ptrn_lines = 1; + else { + p_ptrn_lines = 0; + p_first = 1; + } + if (p_first >= LINENUM_MAX - p_ptrn_lines || + p_ptrn_lines >= LINENUM_MAX - 6) + malformed(); + + /* we need this much at least */ + p_max = p_ptrn_lines + 6; + while (p_max >= hunkmax) + grow_hunkmax(); + p_max = hunkmax; + break; + case '-': + if (buf[1] == '-') { + if (repl_beginning || + (p_end != p_ptrn_lines + 1 + + (p_char[p_end - 1] == '\n'))) { + if (p_end == 1) { + /* + * `old' lines were omitted; + * set up to fill them in + * from 'new' context lines. + */ + p_end = p_ptrn_lines + 1; + fillsrc = p_end + 1; + filldst = 1; + fillcnt = p_ptrn_lines; + } else { + if (repl_beginning) { + if (repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + fatal("duplicate \"---\" at line %ld--check line numbers at line %ld\n", + p_input_line, p_hunk_beg + repl_beginning); + } else { + fatal("%s \"---\" at line %ld--check line numbers at line %ld\n", + (p_end <= p_ptrn_lines + ? "Premature" + : "Overdue"), + p_input_line, p_hunk_beg); + } + } + } + repl_beginning = p_end; + repl_backtrack_position = ftell(pfp); + repl_patch_line = p_input_line; + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + p_char[p_end] = '='; + for (s = buf; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_newfirst = getskiplinenum(&s); + if (*s == ',') { + for (; *s && !isdigit((unsigned char)*s); s++) + ; + if (!*s) + malformed(); + p_repl_lines = (getlinenum(s)) - + p_newfirst + 1; + if (p_repl_lines < 0) + malformed(); + } else if (p_newfirst) + p_repl_lines = 1; + else { + p_repl_lines = 0; + p_newfirst = 1; + } + if (p_newfirst >= LINENUM_MAX - p_repl_lines || + p_repl_lines >= LINENUM_MAX - p_end) + malformed(); + p_max = p_repl_lines + p_end; + if (p_max > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_max, p_input_line, buf); + while (p_max >= hunkmax) + grow_hunkmax(); + if (p_repl_lines != ptrn_copiable && + (p_context != 0 || p_repl_lines != 1)) + repl_could_be_missing = false; + break; + } + goto change_line; + case '+': + case '!': + repl_could_be_missing = false; + change_line: + if (buf[1] == '\n' && canonicalize) + strlcpy(buf + 1, " \n", buf_len - 1); + if (!isspace((unsigned char)buf[1]) && buf[1] != '>' && + buf[1] != '<' && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + if (context >= 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end == p_ptrn_lines) { + if (remove_special_line()) { + int len; + + len = strlen(p_line[p_end]) - 1; + (p_line[p_end])[len] = 0; + } + } + break; + case '\t': + case '\n': /* assume the 2 spaces got eaten */ + if (repl_beginning && repl_could_be_missing && + (!ptrn_spaces_eaten || + diff_type == NEW_CONTEXT_DIFF)) { + repl_missing = true; + goto hunk_done; + } + p_line[p_end] = savestr(buf); + if (out_of_mem) { + p_end--; + return false; + } + if (p_end != p_ptrn_lines + 1) { + ptrn_spaces_eaten |= (repl_beginning != 0); + context++; + if (!repl_beginning) + ptrn_copiable++; + p_char[p_end] = ' '; + } + break; + case ' ': + if (!isspace((unsigned char)buf[1]) && + repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + context++; + if (!repl_beginning) + ptrn_copiable++; + p_line[p_end] = savestr(buf + 2); + if (out_of_mem) { + p_end--; + return false; + } + break; + default: + if (repl_beginning && repl_could_be_missing) { + repl_missing = true; + goto hunk_done; + } + malformed(); + } + /* set up p_len for strncmp() so we don't have to */ + /* assume null termination */ + if (p_line[p_end]) + p_len[p_end] = strlen(p_line[p_end]); + else + p_len[p_end] = 0; + } + +hunk_done: + if (p_end >= 0 && !repl_beginning) + fatal("no --- found in patch at line %ld\n", pch_hunk_beg()); + + if (repl_missing) { + + /* reset state back to just after --- */ + p_input_line = repl_patch_line; + for (p_end--; p_end > repl_beginning; p_end--) + free(p_line[p_end]); + fseek(pfp, repl_backtrack_position, SEEK_SET); + + /* redundant 'new' context lines were omitted - set */ + /* up to fill them in from the old file context */ + if (!p_context && p_repl_lines == 1) { + p_repl_lines = 0; + p_max--; + } + fillsrc = 1; + filldst = repl_beginning + 1; + fillcnt = p_repl_lines; + p_end = p_max; + } else if (!p_context && fillcnt == 1) { + /* the first hunk was a null hunk with no context */ + /* and we were expecting one line -- fix it up. */ + while (filldst < p_end) { + p_line[filldst] = p_line[filldst + 1]; + p_char[filldst] = p_char[filldst + 1]; + p_len[filldst] = p_len[filldst + 1]; + filldst++; + } +#if 0 + repl_beginning--; /* this doesn't need to be fixed */ +#endif + p_end--; + p_first++; /* do append rather than insert */ + fillcnt = 0; + p_ptrn_lines = 0; + } + if (diff_type == CONTEXT_DIFF && + (fillcnt || (p_first > 1 && ptrn_copiable > 2 * p_context))) { + if (verbose) + say("%s\n%s\n%s\n", + "(Fascinating--this is really a new-style context diff but without", + "the telltale extra asterisks on the *** line that usually indicate", + "the new style...)"); + diff_type = NEW_CONTEXT_DIFF; + } + /* if there were omitted context lines, fill them in now */ + if (fillcnt) { + p_bfake = filldst; /* remember where not to free() */ + p_efake = filldst + fillcnt - 1; + while (fillcnt-- > 0) { + while (fillsrc <= p_end && p_char[fillsrc] != ' ') + fillsrc++; + if (fillsrc > p_end) + fatal("replacement text or line numbers mangled in hunk at line %ld\n", + p_hunk_beg); + p_line[filldst] = p_line[fillsrc]; + p_char[filldst] = p_char[fillsrc]; + p_len[filldst] = p_len[fillsrc]; + fillsrc++; + filldst++; + } + while (fillsrc <= p_end && fillsrc != repl_beginning && + p_char[fillsrc] != ' ') + fillsrc++; +#ifdef DEBUGGING + if (debug & 64) + printf("fillsrc %ld, filldst %ld, rb %ld, e+1 %ld\n", + fillsrc, filldst, repl_beginning, p_end + 1); +#endif + if (fillsrc != p_end + 1 && fillsrc != repl_beginning) + malformed(); + if (filldst != p_end + 1 && filldst != repl_beginning) + malformed(); + } + if (p_line[p_end] != NULL) { + if (remove_special_line()) { + p_len[p_end] -= 1; + (p_line[p_end])[p_len[p_end]] = 0; + } + } + } else if (diff_type == UNI_DIFF) { + LINENUM fillold; /* index of old lines */ + LINENUM fillnew; /* index of new lines */ + char ch; + + line_beginning = ftell(pfp); /* file pos of the current line */ + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL || strnNE(buf, "@@ -", 4)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + s = buf + 4; + if (!*s) + malformed(); + p_first = getskiplinenum(&s); + if (*s == ',') { + s++; + p_ptrn_lines = getskiplinenum(&s); + } else + p_ptrn_lines = 1; + if (p_first >= LINENUM_MAX - p_ptrn_lines) + malformed(); + if (*s == ' ') + s++; + if (*s != '+' || !*++s) + malformed(); + p_newfirst = getskiplinenum(&s); + if (*s == ',') { + s++; + p_repl_lines = getskiplinenum(&s); + } else + p_repl_lines = 1; + if (*s == ' ') + s++; + if (*s != '@') + malformed(); + if (p_first >= LINENUM_MAX - p_ptrn_lines || + p_newfirst > LINENUM_MAX - p_repl_lines || + p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1) + malformed(); + if (!p_ptrn_lines) + p_first++; /* do append rather than insert */ + p_max = p_ptrn_lines + p_repl_lines + 1; + while (p_max >= hunkmax) + grow_hunkmax(); + fillold = 1; + fillnew = fillold + p_ptrn_lines; + p_end = fillnew + p_repl_lines; + snprintf(buf, buf_len, "*** %ld,%ld ****\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + snprintf(buf, buf_len, "--- %ld,%ld ----\n", p_newfirst, + p_newfirst + p_repl_lines - 1); + p_line[fillnew] = savestr(buf); + if (out_of_mem) { + p_end = 0; + return false; + } + p_char[fillnew++] = '='; + p_context = 100; + context = 0; + p_hunk_beg = p_input_line + 1; + while (fillold <= p_ptrn_lines || fillnew <= p_end) { + line_beginning = ftell(pfp); + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL) { + if (p_max - fillnew < 3) { + /* assume blank lines got chopped */ + strlcpy(buf, " \n", buf_len); + } else { + fatal("unexpected end of file in patch\n"); + } + } + if (*buf == '\t' || *buf == '\n') { + ch = ' '; /* assume the space got eaten */ + s = savestr(buf); + } else { + ch = *buf; + s = savestr(buf + 1); + } + if (out_of_mem) { + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + return false; + } + switch (ch) { + case '-': + if (fillold > p_ptrn_lines) { + free(s); + p_end = fillnew - 1; + malformed(); + } + p_char[fillold] = ch; + p_line[fillold] = s; + p_len[fillold++] = strlen(s); + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillold - 1] -= 1; + s[p_len[fillold - 1]] = 0; + } + } + break; + case '=': + ch = ' '; + /* FALL THROUGH */ + case ' ': + if (fillold > p_ptrn_lines) { + free(s); + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + malformed(); + } + context++; + p_char[fillold] = ch; + p_line[fillold] = s; + p_len[fillold++] = strlen(s); + s = savestr(s); + if (out_of_mem) { + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + return false; + } + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillold - 1] -= 1; + s[p_len[fillold - 1]] = 0; + } + } + /* FALL THROUGH */ + case '+': + if (fillnew > p_end) { + free(s); + while (--fillnew > p_ptrn_lines) + free(p_line[fillnew]); + p_end = fillold - 1; + malformed(); + } + p_char[fillnew] = ch; + p_line[fillnew] = s; + p_len[fillnew++] = strlen(s); + if (fillold > p_ptrn_lines) { + if (remove_special_line()) { + p_len[fillnew - 1] -= 1; + s[p_len[fillnew - 1]] = 0; + } + } + break; + default: + p_end = fillnew; + malformed(); + } + if (ch != ' ' && context > 0) { + if (context < p_context) + p_context = context; + context = -1000; + } + } /* while */ + } else { /* normal diff--fake it up */ + char hunk_type; + int i; + LINENUM min, max; + + line_beginning = ftell(pfp); + p_context = 0; + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL || !isdigit((unsigned char)*buf)) { + next_intuit_at(line_beginning, p_input_line); + return false; + } + s = buf; + p_first = getskiplinenum(&s); + if (*s == ',') { + s++; + p_ptrn_lines = getskiplinenum(&s) - p_first + 1; + } else + p_ptrn_lines = (*s != 'a'); + if (p_first >= LINENUM_MAX - p_ptrn_lines) + malformed(); + hunk_type = *s++; + if (hunk_type == 'a') + p_first++; /* do append rather than insert */ + min = getskiplinenum(&s); + if (*s == ',') + max = getlinenum(++s); + else + max = min; + if (min < 0 || min > max || max - min == LINENUM_MAX) + malformed(); + if (hunk_type == 'd') + min++; + p_end = p_ptrn_lines + 1 + max - min + 1; + p_newfirst = min; + p_repl_lines = max - min + 1; + if (p_newfirst > LINENUM_MAX - p_repl_lines || + p_ptrn_lines >= LINENUM_MAX - p_repl_lines - 1) + malformed(); + p_end = p_ptrn_lines + p_repl_lines + 1; + if (p_end > MAXHUNKSIZE) + fatal("hunk too large (%ld lines) at line %ld: %s", + p_end, p_input_line, buf); + while (p_end >= hunkmax) + grow_hunkmax(); + snprintf(buf, buf_len, "*** %ld,%ld\n", p_first, + p_first + p_ptrn_lines - 1); + p_line[0] = savestr(buf); + if (out_of_mem) { + p_end = -1; + return false; + } + p_char[0] = '*'; + for (i = 1; i <= p_ptrn_lines; i++) { + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '<') + fatal("< expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '-'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + if (hunk_type == 'c') { + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '-') + fatal("--- expected at line %ld of patch\n", + p_input_line); + } + snprintf(buf, buf_len, "--- %ld,%ld\n", min, max); + p_line[i] = savestr(buf); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_char[i] = '='; + for (i++; i <= p_end; i++) { + ret = pgets(buf, buf_len, pfp); + p_input_line++; + if (ret == NULL) + fatal("unexpected end of file in patch at line %ld\n", + p_input_line); + if (*buf != '>') + fatal("> expected at line %ld of patch\n", + p_input_line); + p_line[i] = savestr(buf + 2); + if (out_of_mem) { + p_end = i - 1; + return false; + } + p_len[i] = strlen(p_line[i]); + p_char[i] = '+'; + } + + if (remove_special_line()) { + p_len[i - 1] -= 1; + (p_line[i - 1])[p_len[i - 1]] = 0; + } + } + if (reverse) /* backwards patch? */ + if (!pch_swap()) + say("Not enough memory to swap next hunk!\n"); +#ifdef DEBUGGING + if (debug & 2) { + int i; + char special; + + for (i = 0; i <= p_end; i++) { + if (i == p_ptrn_lines) + special = '^'; + else + special = ' '; + fprintf(stderr, "%3d %c %c %s", i, p_char[i], + special, p_line[i]); + fflush(stderr); + } + } +#endif + if (p_end + 1 < hunkmax)/* paranoia reigns supreme... */ + p_char[p_end + 1] = '^'; /* add a stopper for apply_hunk */ + return true; +} + +/* + * Input a line from the patch file, worrying about indentation. + */ +static char * +pgets(char *bf, int sz, FILE *fp) +{ + char *s, *ret = fgets(bf, sz, fp); + int indent = 0; + + if (p_indent && ret != NULL) { + for (s = buf; + indent < p_indent && (*s == ' ' || *s == '\t' || *s == 'X'); + s++) { + if (*s == '\t') + indent += 8 - (indent % 7); + else + indent++; + } + if (buf != s && strlcpy(buf, s, buf_len) >= buf_len) + fatal("buffer too small in pgets()\n"); + } + return ret; +} + +/* + * Reverse the old and new portions of the current hunk. + */ +bool +pch_swap(void) +{ + char **tp_line; /* the text of the hunk */ + short *tp_len; /* length of each line */ + char *tp_char; /* +, -, and ! */ + LINENUM i; + LINENUM n; + bool blankline = false; + char *s; + + i = p_first; + p_first = p_newfirst; + p_newfirst = i; + + /* make a scratch copy */ + + tp_line = p_line; + tp_len = p_len; + tp_char = p_char; + p_line = NULL; /* force set_hunkmax to allocate again */ + p_len = NULL; + p_char = NULL; + set_hunkmax(); + if (p_line == NULL || p_len == NULL || p_char == NULL) { + + free(p_line); + p_line = tp_line; + free(p_len); + p_len = tp_len; + free(p_char); + p_char = tp_char; + return false; /* not enough memory to swap hunk! */ + } + /* now turn the new into the old */ + + i = p_ptrn_lines + 1; + if (tp_char[i] == '\n') { /* account for possible blank line */ + blankline = true; + i++; + } + if (p_efake >= 0) { /* fix non-freeable ptr range */ + if (p_efake <= i) + n = p_end - i + 1; + else + n = -i; + p_efake += n; + p_bfake += n; + } + for (n = 0; i <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '+') + p_char[n] = '-'; + p_len[n] = tp_len[i]; + } + if (blankline) { + i = p_ptrn_lines + 1; + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + p_len[n] = tp_len[i]; + n++; + } + if (p_char[0] != '=') + fatal("Malformed patch at line %ld: expected '=' found '%c'\n", + p_input_line, p_char[0]); + p_char[0] = '*'; + for (s = p_line[0]; *s; s++) + if (*s == '-') + *s = '*'; + + /* now turn the old into the new */ + + if (p_char[0] != '*') + fatal("Malformed patch at line %ld: expected '*' found '%c'\n", + p_input_line, p_char[0]); + tp_char[0] = '='; + for (s = tp_line[0]; *s; s++) + if (*s == '*') + *s = '-'; + for (i = 0; n <= p_end; i++, n++) { + p_line[n] = tp_line[i]; + p_char[n] = tp_char[i]; + if (p_char[n] == '-') + p_char[n] = '+'; + p_len[n] = tp_len[i]; + } + + if (i != p_ptrn_lines + 1) + fatal("Malformed patch at line %ld: expected %ld lines, " + "got %ld\n", + p_input_line, p_ptrn_lines + 1, i); + + i = p_ptrn_lines; + p_ptrn_lines = p_repl_lines; + p_repl_lines = i; + + free(tp_line); + free(tp_len); + free(tp_char); + + return true; +} + +/* + * Return the specified line position in the old file of the old context. + */ +LINENUM +pch_first(void) +{ + return p_first; +} + +/* + * Return the number of lines of old context. + */ +LINENUM +pch_ptrn_lines(void) +{ + return p_ptrn_lines; +} + +/* + * Return the probable line position in the new file of the first line. + */ +LINENUM +pch_newfirst(void) +{ + return p_newfirst; +} + +/* + * Return the number of lines in the replacement text including context. + */ +LINENUM +pch_repl_lines(void) +{ + return p_repl_lines; +} + +/* + * Return the number of lines in the whole hunk. + */ +LINENUM +pch_end(void) +{ + return p_end; +} + +/* + * Return the number of context lines before the first changed line. + */ +LINENUM +pch_context(void) +{ + return p_context; +} + +/* + * Return the length of a particular patch line. + */ +short +pch_line_len(LINENUM line) +{ + return p_len[line]; +} + +/* + * Return the control character (+, -, *, !, etc) for a patch line. + */ +char +pch_char(LINENUM line) +{ + return p_char[line]; +} + +/* + * Return a pointer to a particular patch line. + */ +char * +pfetch(LINENUM line) +{ + return p_line[line]; +} + +/* + * Return where in the patch file this hunk began, for error messages. + */ +LINENUM +pch_hunk_beg(void) +{ + return p_hunk_beg; +} + +/* + * Apply an ed script by feeding ed itself. + */ +void +do_ed_script(void) +{ + char *t; + long beginning_of_this_line; + FILE *pipefp = NULL; + int continuation; + + if (!skip_rest_of_patch) { + if (copy_file(filearg[0], TMPOUTNAME) < 0) { + unlink(TMPOUTNAME); + fatal("can't create temp file %s", TMPOUTNAME); + } + snprintf(buf, buf_len, "%s -S%s %s", _PATH_ED, + verbose ? "" : "s", TMPOUTNAME); + pipefp = popen(buf, "w"); + } + for (;;) { + beginning_of_this_line = ftell(pfp); + if (pgets(buf, buf_len, pfp) == NULL) { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + p_input_line++; + for (t = buf; isdigit((unsigned char)*t) || *t == ','; t++) + ; + /* POSIX defines allowed commands as {a,c,d,i,s} */ + if (isdigit((unsigned char)*buf) && (*t == 'a' || *t == 'c' || + *t == 'd' || *t == 'i' || *t == 's')) { + if (pipefp != NULL) + fputs(buf, pipefp); + if (*t == 's') { + for (;;) { + continuation = 0; + t = strchr(buf, '\0') - 1; + while (--t >= buf && *t == '\\') + continuation = !continuation; + if (!continuation || + pgets(buf, sizeof buf, pfp) == NULL) + break; + if (pipefp != NULL) + fputs(buf, pipefp); + } + } else if (*t != 'd') { + while (pgets(buf, buf_len, pfp) != NULL) { + p_input_line++; + if (pipefp != NULL) + fputs(buf, pipefp); + if (strEQ(buf, ".\n")) + break; + } + } + } else { + next_intuit_at(beginning_of_this_line, p_input_line); + break; + } + } + if (pipefp == NULL) + return; + fprintf(pipefp, "w\n"); + fprintf(pipefp, "q\n"); + fflush(pipefp); + pclose(pipefp); + ignore_signals(); + if (!check_only) { + if (move_file(TMPOUTNAME, outname) < 0) { + toutkeep = true; + chmod(TMPOUTNAME, filemode); + } else + chmod(outname, filemode); + } + set_signals(1); +} + +/* + * Choose the name of the file to be patched based on POSIX rules. + * NOTE: the POSIX rules are amazingly stupid and we only follow them + * if the user specified --posix or set POSIXLY_CORRECT. + */ +static char * +posix_name(const struct file_name *names, bool assume_exists) +{ + char *path = NULL; + int i; + + /* + * POSIX states that the filename will be chosen from one + * of the old, new and index names (in that order) if + * the file exists relative to CWD after -p stripping. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && names[i].exists) { + path = names[i].path; + break; + } + } + if (path == NULL && !assume_exists) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Same order as above. + */ + for (i = 0; i < MAX_FILE; i++) { + if (names[i].path != NULL && + (path = checked_in(names[i].path)) != NULL) + break; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (path == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + path = names[NEW_FILE].path; + } + + return path ? savestr(path) : NULL; +} + +/* + * Choose the name of the file to be patched based the "best" one + * available. + */ +static char * +best_name(const struct file_name *names, bool assume_exists) +{ + size_t min_components, min_baselen, min_len, tmp; + char *best = NULL; + int i; + + /* + * The "best" name is the one with the fewest number of path + * components, the shortest basename length, and the shortest + * overall length (in that order). We only use the Index: file + * if neither of the old or new files could be intuited from + * the diff header. + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + (!names[i].exists && !assume_exists)) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + if (best == NULL) { + /* + * No files found, look for something we can checkout from + * RCS/SCCS dirs. Logic is identical to that above... + */ + min_components = min_baselen = min_len = SIZE_MAX; + for (i = INDEX_FILE; i >= OLD_FILE; i--) { + if (names[i].path == NULL || + checked_in(names[i].path) == NULL) + continue; + if ((tmp = num_components(names[i].path)) > min_components) + continue; + min_components = tmp; + if ((tmp = strlen(basename(names[i].path))) > min_baselen) + continue; + min_baselen = tmp; + if ((tmp = strlen(names[i].path)) > min_len) + continue; + min_len = tmp; + best = names[i].path; + } + /* + * Still no match? Check to see if the diff could be creating + * a new file. + */ + if (best == NULL && ok_to_create_file && + names[NEW_FILE].path != NULL) + best = names[NEW_FILE].path; + } + + return best ? savestr(best) : NULL; +} + +static size_t +num_components(const char *path) +{ + size_t n; + const char *cp; + + for (n = 0, cp = path; (cp = strchr(cp, '/')) != NULL; n++, cp++) { + while (*cp == '/') + cp++; /* skip consecutive slashes */ + } + return n; +} diff --git a/usr.bin/patch/pch.h b/usr.bin/patch/pch.h new file mode 100644 index 0000000..a2d45a7 --- /dev/null +++ b/usr.bin/patch/pch.h @@ -0,0 +1,59 @@ +/* + * $OpenBSD: pch.h,v 1.9 2003/10/31 20:20:45 millert Exp $ + * $DragonFly: src/usr.bin/patch/pch.h,v 1.1 2004/09/24 18:44:28 joerg Exp $ + * $NetBSD: pch.h,v 1.10 2008/09/19 18:33:34 joerg Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#define OLD_FILE 0 +#define NEW_FILE 1 +#define INDEX_FILE 2 +#define MAX_FILE 3 + +struct file_name { + char *path; + bool exists; +}; + +void re_patch(void); +void open_patch_file(const char *); +void set_hunkmax(void); +bool there_is_another_patch(void); +bool another_hunk(void); +bool pch_swap(void); +char *pfetch(LINENUM); +short pch_line_len(LINENUM); +LINENUM pch_first(void); +LINENUM pch_ptrn_lines(void); +LINENUM pch_newfirst(void); +LINENUM pch_repl_lines(void); +LINENUM pch_end(void); +LINENUM pch_context(void); +LINENUM pch_hunk_beg(void); +char pch_char(LINENUM); +void do_ed_script(void); diff --git a/usr.bin/patch/util.c b/usr.bin/patch/util.c new file mode 100644 index 0000000..30e4f3c --- /dev/null +++ b/usr.bin/patch/util.c @@ -0,0 +1,447 @@ +/* + * $OpenBSD: util.c,v 1.32 2006/03/11 19:41:30 otto Exp $ + * $DragonFly: src/usr.bin/patch/util.c,v 1.9 2007/09/29 23:11:10 swildner Exp $ + * $NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +#include +__RCSID("$NetBSD: util.c,v 1.28 2018/06/18 18:33:31 christos Exp $"); + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "util.h" +#include "backupfile.h" +#include "pathnames.h" + +/* Rename a file, copying it if necessary. */ + +int +move_file(const char *from, const char *to) +{ + int fromfd; + ssize_t i; + + /* to stdout? */ + + if (strEQ(to, "-")) { +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to stdout.\n", from); +#endif + fromfd = open(from, O_RDONLY); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, buf_len)) > 0) + if (write(STDOUT_FILENO, buf, i) != i) + pfatal("write failed"); + close(fromfd); + return 0; + } + if (backup_file(to) < 0) { + say("Can't backup %s, output is in %s: %s\n", to, from, + strerror(errno)); + return -1; + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", from, to); +#endif + if (rename(from, to) < 0) { + if (errno != EXDEV || copy_file(from, to) < 0) { + say("Can't create %s, output is in %s: %s\n", + to, from, strerror(errno)); + return -1; + } + } + return 0; +} + +/* Backup the original file. */ + +int +backup_file(const char *orig) +{ + struct stat filestat; + char bakname[MAXPATHLEN], *s, *simplename; + dev_t orig_device; + ino_t orig_inode; + + if (backup_type == none || stat(orig, &filestat) != 0) + return 0; /* nothing to do */ + /* + * If the user used zero prefixes or suffixes, then + * he doesn't want backups. Yet we have to remove + * orig to break possible hardlinks. + */ + if ((origprae && *origprae == 0) || *simple_backup_suffix == 0) { + unlink(orig); + return 0; + } + orig_device = filestat.st_dev; + orig_inode = filestat.st_ino; + + if (origprae) { + if (strlcpy(bakname, origprae, sizeof(bakname)) >= sizeof(bakname) || + strlcat(bakname, orig, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", origprae); + } else { + if ((s = find_backup_file_name(orig)) == NULL) + fatal("out of memory\n"); + if (strlcpy(bakname, s, sizeof(bakname)) >= sizeof(bakname)) + fatal("filename %s too long for buffer\n", s); + free(s); + } + + if ((simplename = strrchr(bakname, '/')) != NULL) + simplename = simplename + 1; + else + simplename = bakname; + + /* + * Find a backup name that is not the same file. Change the + * first lowercase char into uppercase; if that isn't + * sufficient, chop off the first char and try again. + */ + while (stat(bakname, &filestat) == 0 && + orig_device == filestat.st_dev && orig_inode == filestat.st_ino) { + /* Skip initial non-lowercase chars. */ + for (s = simplename; *s && !islower((unsigned char)*s); s++) + ; + if (*s) + *s = toupper((unsigned char)*s); + else + memmove(simplename, simplename + 1, + strlen(simplename + 1) + 1); + } +#ifdef DEBUGGING + if (debug & 4) + say("Moving %s to %s.\n", orig, bakname); +#endif + if (rename(orig, bakname) < 0) { + if (errno != EXDEV || copy_file(orig, bakname) < 0) + return -1; + } + return 0; +} + +/* + * Copy a file. + */ +int +copy_file(const char *from, const char *to) +{ + int tofd, fromfd; + ssize_t i; + + tofd = open(to, O_CREAT|O_TRUNC|O_WRONLY, 0666); + if (tofd < 0) + return -1; + fromfd = open(from, O_RDONLY, 0); + if (fromfd < 0) + pfatal("internal error, can't reopen %s", from); + while ((i = read(fromfd, buf, buf_len)) > 0) + if (write(tofd, buf, i) != i) + pfatal("write to %s failed", to); + close(fromfd); + close(tofd); + return 0; +} + +/* + * Allocate a unique area for a string. + */ +char * +savestr(const char *s) +{ + char *rv; + + if (!s) + s = "Oops"; + rv = strdup(s); + if (rv == NULL) { + if (using_plan_a) + out_of_mem = true; + else + fatal("out of memory\n"); + } + return rv; +} + +/* + * Vanilla terminal output (buffered). + */ +void +say(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fflush(stderr); +} + +/* + * Terminal output, pun intended. + */ +void +fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + fprintf(stderr, "patch: **** "); + vfprintf(stderr, fmt, ap); + va_end(ap); + my_exit(2); +} + +/* + * Say something from patch, something from the system, then silence . . . + */ +void +pfatal(const char *fmt, ...) +{ + va_list ap; + int errnum = errno; + + fprintf(stderr, "patch: **** "); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, ": %s\n", strerror(errnum)); + my_exit(2); +} + +/* + * Get a response from the user via /dev/tty + */ +void +ask(const char *fmt, ...) +{ + va_list ap; + ssize_t nr = 0; + static int ttyfd = -1; + + va_start(ap, fmt); + vfprintf(stdout, fmt, ap); + va_end(ap); + fflush(stdout); + if (ttyfd < 0) + ttyfd = open(_PATH_TTY, O_RDONLY); + if (ttyfd >= 0) { + if ((nr = read(ttyfd, buf, buf_len)) > 0 && + buf[nr - 1] == '\n') + buf[nr - 1] = '\0'; + } + if (ttyfd < 0 || nr <= 0) { + /* no tty or error reading, pretend user entered 'return' */ + putchar('\n'); + buf[0] = '\0'; + } +} + +/* + * How to handle certain events when not in a critical region. + */ +void +set_signals(int reset) +{ + static sig_t hupval, intval; + + if (!reset) { + hupval = signal(SIGHUP, SIG_IGN); + if (hupval != SIG_IGN) + hupval = my_exit; + intval = signal(SIGINT, SIG_IGN); + if (intval != SIG_IGN) + intval = my_exit; + } + signal(SIGHUP, hupval); + signal(SIGINT, intval); +} + +/* + * How to handle certain events when in a critical region. + */ +void +ignore_signals(void) +{ + signal(SIGHUP, SIG_IGN); + signal(SIGINT, SIG_IGN); +} + +/* + * Make sure we'll have the directories to create a file. If `striplast' is + * true, ignore the last element of `filename'. + */ + +void +makedirs(const char *filename, bool striplast) +{ + char *tmpbuf; + + if ((tmpbuf = strdup(filename)) == NULL) + fatal("out of memory\n"); + + if (striplast) { + char *s = strrchr(tmpbuf, '/'); + if (s == NULL) { + free(tmpbuf); + return; /* nothing to be done */ + } + *s = '\0'; + } + if (mkpath(tmpbuf) != 0) + pfatal("creation of %s failed", tmpbuf); + free(tmpbuf); +} + +/* + * Make filenames more reasonable. + */ +char * +fetchname(const char *at, bool *exists, int strip_leading) +{ + char *fullname, *name, *t; + int sleading, tab; + struct stat filestat; + + if (at == NULL || *at == '\0') + return NULL; + while (isspace((unsigned char)*at)) + at++; +#ifdef DEBUGGING + if (debug & 128) + say("fetchname %s %d\n", at, strip_leading); +#endif + /* So files can be created by diffing against /dev/null. */ + if (strnEQ(at, _PATH_DEVNULL, sizeof(_PATH_DEVNULL) - 1)) + return NULL; + name = fullname = t = savestr(at); + + tab = strchr(t, '\t') != NULL; + /* Strip off up to `strip_leading' path components and NUL terminate. */ + for (sleading = strip_leading; *t != '\0' && ((tab && *t != '\t') || + !isspace((unsigned char)*t)); t++) { + if (t[0] == '/' && t[1] != '/' && t[1] != '\0') + if (--sleading >= 0) + name = t + 1; + } + *t = '\0'; + + /* + * If no -p option was given (957 is the default value!), we were + * given a relative pathname, and the leading directories that we + * just stripped off all exist, put them back on. + */ + if (strip_leading == 957 && name != fullname && *fullname != '/') { + name[-1] = '\0'; + if (stat(fullname, &filestat) == 0 && S_ISDIR(filestat.st_mode)) { + name[-1] = '/'; + name = fullname; + } + } + name = savestr(name); + free(fullname); + + *exists = stat(name, &filestat) == 0; + return name; +} + +/* + * Takes the name returned by fetchname and looks in RCS/SCCS directories + * for a checked in version. + */ +char * +checked_in(char *file) +{ + char *filebase, *filedir, tmpbuf[MAXPATHLEN]; + struct stat filestat; + + filebase = basename(file); + filedir = dirname(file); + +#define try(f, a1, a2, a3) \ +(snprintf(tmpbuf, sizeof tmpbuf, f, a1, a2, a3), stat(tmpbuf, &filestat) == 0) + + if (try("%s/RCS/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/RCS/%s%s", filedir, filebase, "") || + try("%s/%s%s", filedir, filebase, RCSSUFFIX) || + try("%s/SCCS/%s%s", filedir, SCCSPREFIX, filebase) || + try("%s/%s%s", filedir, SCCSPREFIX, filebase)) + return file; + + return NULL; +} + +void +version(void) +{ + printf("Patch version 2.0-12u8-NetBSD\n"); + my_exit(EXIT_SUCCESS); +} + +/* + * Exit with cleanup. + */ +void +my_exit(int status) +{ + unlink(TMPINNAME); + if (!toutkeep) + unlink(TMPOUTNAME); + if (!trejkeep) + unlink(TMPREJNAME); + unlink(TMPPATNAME); + exit(status); +} + +void * +pch_realloc(void *ptr, size_t number, size_t size) +{ + if (number > SIZE_MAX / size) { + errno = EOVERFLOW; + return NULL; + } + return realloc(ptr, number * size); +} diff --git a/usr.bin/patch/util.h b/usr.bin/patch/util.h new file mode 100644 index 0000000..d8bde6c --- /dev/null +++ b/usr.bin/patch/util.h @@ -0,0 +1,51 @@ +/* + * $OpenBSD: util.h,v 1.15 2005/06/20 07:14:06 otto Exp $ + * $DragonFly: src/usr.bin/patch/util.h,v 1.2 2007/09/29 23:11:10 swildner Exp $ + * $NetBSD: util.h,v 1.13 2018/06/18 18:33:31 christos Exp $ + */ + +/* + * patch - a program to apply diffs to original files + * + * Copyright 1986, Larry Wall + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * 1. Redistributions of source code must retain the above copyright notice, + * this condition and the following disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * -C option added in 1998, original code by Marc Espie, based on FreeBSD + * behaviour + */ + +char *fetchname(const char *, bool *, int); +char *checked_in(char *); +int backup_file(const char *); +int move_file(const char *, const char *); +int copy_file(const char *, const char *); +void say(const char *, ...) __printflike(1, 2); +void fatal(const char *, ...) __printflike(1, 2) __dead; +void pfatal(const char *, ...) __printflike(1, 2) __dead; +void ask(const char *, ...) __printflike(1, 2); +char *savestr(const char *); +void set_signals(int); +void ignore_signals(void); +void makedirs(const char *, bool); +void version(void) __dead; +void my_exit(int) __dead; +void *pch_realloc(void *, size_t, size_t); + +/* in mkpath.c */ +extern int mkpath(char *); diff --git a/usr.bin/pathchk/pathchk.1 b/usr.bin/pathchk/pathchk.1 new file mode 100644 index 0000000..d7f5a43 --- /dev/null +++ b/usr.bin/pathchk/pathchk.1 @@ -0,0 +1,123 @@ +.\" $NetBSD: pathchk.1,v 1.6 2010/11/16 23:59:02 wiz Exp $ +.\" +.\" Copyright (c) 2001, 2002 Chuck Rouillard +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. The name of the author may not be used to endorse or promote +.\" products derived from this software without specific prior written +.\" permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from FreeBSD: pathchk.1,v 1.3 2002/12/12 17:26:01 ru Exp +.\" +.Dd November 9, 2010 +.Dt PATHCHK 1 +.Os +.Sh NAME +.Nm pathchk +.Nd check pathnames +.Sh SYNOPSIS +.Nm +.Op Fl p +.Ar pathname ... +.Sh DESCRIPTION +The +.Nm +utility checks whether each of the specified +.Ar pathname +arguments is valid or portable. +.Pp +A diagnostic message is written for each argument that: +.Bl -bullet +.It +Is longer than +.Dv PATH_MAX +bytes. +.It +Contains any component longer than +.Dv NAME_MAX +bytes. +(The value of +.Dv NAME_MAX +depends on the underlying file system.) +.It +Contains a directory component that is not searchable. +.El +.Pp +It is not considered an error if a +.Ar pathname +argument contains a nonexistent component as long as a component by that +name could be created. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl p +Perform portability checks on the specified +.Ar pathname +arguments. +Diagnostic messages will be written for each argument that: +.Bl -bullet +.It +Is longer than +.Dv _POSIX_PATH_MAX +.Pq 255 +bytes. +.It +Contains a component longer than +.Dv _POSIX_NAME_MAX +.Pq 14 +bytes. +.It +Contains any character not in the portable filename character set (that is, +alphanumeric characters, +.Ql \&. , +.Ql \&- +and +.Ql _ ) . +No component may start with the hyphen +.Pq Ql \&- +character. +.El +.El +.Sh EXAMPLES +Check whether the names of files in the current directory are portable to +other +.Tn POSIX +systems: +.Pp +.Dl "find . -exec pathchk -p \e{\e} +" +or the more efficient: +.Dl "find . -print0 | xargs -0 pathchk -p" +.Sh SEE ALSO +.Xr getconf 1 , +.Xr pathconf 2 , +.Xr stat 2 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2001 . +.Sh HISTORY +A +.Nm +utility appeared in +.Nx 2.0 . diff --git a/usr.bin/pathchk/pathchk.c b/usr.bin/pathchk/pathchk.c new file mode 100644 index 0000000..0f7f5c1 --- /dev/null +++ b/usr.bin/pathchk/pathchk.c @@ -0,0 +1,191 @@ +/* $NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $ */ + +/*- + * Copyright (c) 2002 Tim J. Robbins. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from FreeBSD: pathchk.c,v 1.4 2002/12/15 00:40:47 tjr Exp + */ + +#include +#ifndef lint +__RCSID("$NetBSD: pathchk.c,v 1.2 2011/09/16 15:39:28 joerg Exp $"); +#endif /* !lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +static int check(const char *); +static int portable(const char *); +__dead static void usage(void); + +static int pflag; /* Perform portability checks */ + +int +main(int argc, char *argv[]) +{ + int ch, rval; + const char *arg; + + while ((ch = getopt(argc, argv, "p")) > 0) { + switch (ch) { + case 'p': + pflag = 1; + break; + default: + usage(); + /*NOTREACHED*/ + } + } + argc -= optind; + argv += optind; + + if (argc == 0) + usage(); + + rval = 0; + while ((arg = *argv++) != NULL) + rval |= check(arg); + + exit(rval); +} + +static void +usage(void) +{ + + fprintf(stderr, "usage: pathchk [-p] pathname...\n"); + exit(1); +} + +static int +check(const char *path) +{ + struct stat sb; + long complen, namemax, pathmax, svnamemax; + int badch, last; + char *end, *p, *pathd; + + if ((pathd = strdup(path)) == NULL) + err(1, "strdup"); + + p = pathd; + + if (!pflag) { + errno = 0; + namemax = pathconf(*p == '/' ? "/" : ".", _PC_NAME_MAX); + if (namemax == -1 && errno != 0) + namemax = NAME_MAX; + } else + namemax = _POSIX_NAME_MAX; + + for (;;) { + p += strspn(p, "/"); + complen = (long)strcspn(p, "/"); + end = p + complen; + last = *end == '\0'; + *end = '\0'; + + if (namemax != -1 && complen > namemax) { + warnx("%s: %s: component too long (limit %ld)", path, + p, namemax); + goto bad; + } + + if (!pflag && stat(pathd, &sb) == -1 && errno != ENOENT) { + warn("%s: %.*s", path, (int)(strlen(pathd) - + complen - 1), pathd); + goto bad; + } + + if (pflag && (badch = portable(p)) >= 0) { + warnx("%s: %s: component contains non-portable " + "character `%c'", path, p, badch); + goto bad; + } + + if (last) + break; + + if (!pflag) { + errno = 0; + svnamemax = namemax; + namemax = pathconf(pathd, _PC_NAME_MAX); + if (namemax == -1 && errno != 0) + namemax = svnamemax; + } + + *end = '/'; + p = end + 1; + } + + if (!pflag) { + errno = 0; + pathmax = pathconf(path, _PC_PATH_MAX); + if (pathmax == -1 && errno != 0) + pathmax = PATH_MAX; + } else + pathmax = _POSIX_PATH_MAX; + if (pathmax != -1 && strlen(path) >= (size_t)pathmax) { + warnx("%s: path too long (limit %ld)", path, pathmax - 1); + goto bad; + } + + free(pathd); + return (0); + +bad: free(pathd); + return (1); +} + +/* + * Check whether a path component contains only portable characters. Return + * the first non-portable character found. + */ +static int +portable(const char *path) +{ + static const char charset[] = + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789._-"; + long s; + + if (*path == '-') + return (*path); + + s = strspn(path, charset); + if (path[s] != '\0') + return (path[s]); + + return (-1); +} diff --git a/usr.bin/pr/egetopt.c b/usr.bin/pr/egetopt.c new file mode 100644 index 0000000..5c43476 --- /dev/null +++ b/usr.bin/pr/egetopt.c @@ -0,0 +1,215 @@ +/* $NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $ */ + +/*- + * Copyright (c) 1991 Keith Muller. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +from: static char sccsid[] = "@(#)egetopt.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: egetopt.c,v 1.9 2011/09/06 18:26:06 joerg Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include + +#include "extern.h" + +/* + * egetopt: get option letter from argument vector (an extended + * version of getopt). + * + * Non standard additions to the ostr specs are: + * 1) '?': immediate value following arg is optional (no white space + * between the arg and the value) + * 2) '#': +/- followed by a number (with an optional sign but + * no white space between the arg and the number). The - may be + * combined with other options, but the + cannot. + */ + +int eopterr = 1; /* if error message should be printed */ +int eoptind = 1; /* index into parent argv vector */ +int eoptopt; /* character checked for validity */ +char *eoptarg; /* argument associated with option */ + +#define BADCH (int)'?' +static char EMSG[1] = { '\0' }; + +int +egetopt(int nargc, char * const *nargv, const char *ostr) +{ + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + static int delim; /* which option delimiter */ + char *p; + static char savec = '\0'; + + if (savec != '\0') { + *place = savec; + savec = '\0'; + } + + if (!*place) { + /* + * update scanning pointer + */ + if ((eoptind >= nargc) || + ((*(place = nargv[eoptind]) != '-') && (*place != '+'))) { + place = EMSG; + return (-1); + } + + delim = (int)*place; + if (place[1] && *++place == '-' && !place[1]) { + /* + * found "--" + */ + ++eoptind; + place = EMSG; + return (-1); + } + } + + /* + * check option letter + */ + if ((eoptopt = (int)*place++) == (int)':' || (eoptopt == (int)'?') || + !(oli = strchr(ostr, eoptopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1 when by itself. + */ + if ((eoptopt == (int)'-') && !*place) + return (-1); + if (strchr(ostr, '#') && (isdigit(eoptopt) || + (((eoptopt == (int)'-') || (eoptopt == (int)'+')) && + isdigit((unsigned char)*place)))) { + /* + * # option: +/- with a number is ok + */ + for (p = place; *p != '\0'; ++p) { + if (!isdigit((unsigned char)*p)) + break; + } + eoptarg = place-1; + + if (*p == '\0') { + place = EMSG; + ++eoptind; + } else { + place = p; + savec = *p; + *place = '\0'; + } + return (delim); + } + + if (!*place) + ++eoptind; + if (eopterr) { + if (!(p = strrchr(*nargv, '/'))) + p = *nargv; + else + ++p; + (void)fprintf(stderr, "%s: illegal option -- %c\n", + p, eoptopt); + } + return (BADCH); + } + if (delim == (int)'+') { + /* + * '+' is only allowed with numbers + */ + if (!*place) + ++eoptind; + if (eopterr) { + if (!(p = strrchr(*nargv, '/'))) + p = *nargv; + else + ++p; + (void)fprintf(stderr, + "%s: illegal '+' delimiter with option -- %c\n", + p, eoptopt); + } + return (BADCH); + } + ++oli; + if ((*oli != ':') && (*oli != '?')) { + /* + * don't need argument + */ + eoptarg = NULL; + if (!*place) + ++eoptind; + return (eoptopt); + } + + if (*place) { + /* + * no white space + */ + eoptarg = place; + } else if (*oli == '?') { + /* + * no arg, but NOT required + */ + eoptarg = NULL; + } else if (nargc <= ++eoptind) { + /* + * no arg, but IS required + */ + place = EMSG; + if (eopterr) { + if (!(p = strrchr(*nargv, '/'))) + p = *nargv; + else + ++p; + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", p, + eoptopt); + } + return (BADCH); + } else { + /* + * arg has white space + */ + eoptarg = nargv[eoptind]; + } + place = EMSG; + ++eoptind; + return (eoptopt); +} diff --git a/usr.bin/pr/extern.h b/usr.bin/pr/extern.h new file mode 100644 index 0000000..6aeb61c --- /dev/null +++ b/usr.bin/pr/extern.h @@ -0,0 +1,42 @@ +/* $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $ */ + +/*- + * Copyright (c) 1991 Keith Muller. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)extern.h 8.1 (Berkeley) 6/6/93 + * $NetBSD: extern.h,v 1.6 2011/09/06 18:26:06 joerg Exp $ + */ + +extern int eoptind; +extern char *eoptarg; + +int egetopt(int, char * const *, const char *); diff --git a/usr.bin/pr/pr.1 b/usr.bin/pr/pr.1 new file mode 100644 index 0000000..f1cc1aa --- /dev/null +++ b/usr.bin/pr/pr.1 @@ -0,0 +1,361 @@ +.\" $NetBSD: pr.1,v 1.23 2017/07/03 21:34:21 wiz Exp $ +.\" +.\" Copyright (c) 1991 Keith Muller. +.\" Copyright (c) 1993 +.\" The Regents of the University of California. All rights reserved. +.\" Copyright (c) 1994-1995, 1997, 1999-2003, 2009, 2012 +.\" The NetBSD Foundation, Inc. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Keith Muller of the University of California, San Diego. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)pr.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd July 31, 2012 +.Dt PR 1 +.Os +.Sh NAME +.Nm pr +.Nd print files +.Sh SYNOPSIS +.Nm +.Op Ar \&+page +.Op Fl Ar column +.Op Fl adFfmprt +.Oo +.Fl e Ns Oo Ar char Oc Ns Op Ar gap +.Oc +.Op Fl h Ar header +.Oo +.Fl i Ns Oo Ar char Oc Ns Op Ar gap +.Oc +.Op Fl l Ar lines +.Oo +.Fl n Ns Oo Ar char Oc Ns Op Ar width +.Oc +.Op Fl o Ar offset +.Oo +.Fl s Ns Op Ar char +.Oc +.Op Fl T Ar timefmt +.Op Fl w Ar width +.Op - +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility is a printing and pagination filter for text files. +When multiple input files are specified, each is read, formatted, +and written to standard output. +By default, the input is separated into 66-line pages, each with +.Bl -bullet +.It +A 5-line header with the page number, date, time, and +the pathname of the file. +.It +A 5-line trailer consisting of blank lines. +.El +.Pp +If standard output is associated with a terminal, +diagnostic messages are suppressed until the +.Nm +utility has completed processing. +.Pp +When multiple column output is specified, +text columns are of equal width. +By default text columns are separated by at least one +.Aq Em blank . +Input lines that do not fit into a text column are truncated. +Lines are not truncated under single column output. +.Sh OPTIONS +In the following option descriptions, column, lines, offset, page, and +width are positive decimal integers and gap is a nonnegative decimal integer. +.Bl -tag -width 4n +.It Ar \&+page +Begin output at page number +.Ar page +of the formatted input. +.It Fl Ar column +Produce output that is +.Ar columns +wide (default is 1) that is written vertically +down each column in the order in which the text +is received from the input file. +The options +.Fl e +and +.Fl i +are assumed. +This option should not be used with +.Fl m . +When used with +.Fl t , +the minimum number of lines is used to display the output. +.It Fl a +Modify the effect of the +.Fl column +option so that the columns are filled across the page in a round-robin order +(e.g., when column is 2, the first input line heads column +1, the second heads column 2, the third is the second line +in column 1, etc.). +This option requires the use of the +.Fl column +option. +.It Fl d +Produce output that is double spaced. +An extra +.Aq Em newline +character is output following every +.Aq newline +found in the input. +.It Fl e Ns Oo Ar char Oc Ns Op Ar gap +Expand each input +.Aq tab +to the next greater column +position specified by the formula +.Ar n*gap+1 , +where +.Em n +is an integer > 0. +If +.Ar gap +is zero or is omitted the default is 8. +All +.Aq Em tab +characters in the input are expanded into the appropriate +number of +.Ao Em space Ac Ns s . +If any nondigit character, +.Ar char , +is specified, it is used as the input tab character. +If the first character of +.Ar char +is a digit then +.Ar char +is treated as +.Ar gap . +.It Fl F +Use a +.Aq Em form-feed +character for new pages, +instead of the default behavior that uses a +sequence of +.Aq Em newline +characters. +.It Fl f +Same as +.Fl F . +Additionally pause before beginning the first page +if the standard output is associated with a terminal. +.It Fl h Ar header +Use the string +.Ar header +to replace the +.Ar file name +in the header line. +.It Fl i Ns Oo Ar char Oc Ns Op Ar gap +In output, replace multiple +.Ao space Ac Ns s +with +.Ao tab Ac Ns s +whenever two or more +adjacent +.Ao space Ac Ns s +reach column positions +.Ar gap+1 , +.Ar 2*gap+1 , +etc. +If +.Ar gap +is zero or omitted, default +.Aq Em tab +settings at every eighth column position +is used. +If any nondigit character, +.Ar char , +is specified, it is used as the output +.Aq Em tab +character. +If the first character of +.Ar char +is a digit then +.Ar char +is treated as +.Ar gap . +.It Fl l Ar lines +Override the 66 line default and reset the page length to +.Ar lines . +If +.Ar lines +is not greater than the sum of both the header and trailer +depths (in lines), the +.Nm +utility suppresses output of both the header and trailer, as if the +.Fl t +option were in effect. +.It Fl m +Merge the contents of multiple files. +One line from each file specified by a file operand is +written side by side into text columns of equal fixed widths, in +terms of the number of column positions. +The number of text columns depends on the number of +file operands successfully opened. +The maximum number of files merged depends on page width and the +per process open file limit. +The options +.Fl e +and +.Fl i +are assumed. +.It Fl n Ns Oo Ar char Oc Ns Op Ar width +Provide +.Ar width +digit line numbering. +The default for +.Ar width , +if not specified, is 5. +The number occupies the first +.Ar width +column positions of each text column or each line of +.Fl m +output. +If +.Ar char +(any nondigit character) is given, it is appended to the line number to +separate it from whatever follows. +The default for +.Ar char +is a +.Aq Em tab . +Line numbers longer than +.Ar width +columns are truncated. +.It Fl o Ar offset +Each line of output is preceded by +.Ar offset +.Ao Em space Ac Ns s . +If the +.Fl o +option is not specified, the default is zero. +The space taken is in addition to the output line width. +.It Fl p +Pause before beginning each page if the +standard output is associated with a terminal. +.Nm +will write an +.Aq Em alert +to standard error and wait for a +.Aq Em carriage-return +to be read on +.Pa /dev/tty . +.It Fl r +Write no diagnostic reports on failure to open a file. +.It Fl s Ns Op Ar char +Separate text columns by the single character +.Ar char +instead of by the appropriate number of +.Ao Em space Ac Ns s +(default for +.Ar char +is the +.Aq Em tab +character). +.It Fl T +Specify an +.Xr strftime 3 +format string to be used to format the date and time information in the page +header. +.It Fl t +Print neither the five-line identifying +header nor the five-line trailer usually supplied for each page. +Quit printing after the last line of each file without spacing to the +end of the page. +.It Fl w Ar width +Set the width of the line to +.Ar width +column positions for multiple text-column output only. +If the +.Fl w +option is not specified and the +.Fl s +option is not specified, the default width is 72. +If the +.Fl w +option is not specified and the +.Fl s +option is specified, the default width is 512. +.It Ar file +A pathname of a file to be printed. +If no +.Ar file +operands are specified, or if a +.Ar file +operand is +.Sq Fl , +the standard input is used. +The standard input is used only if no +.Ar file +operands are specified, or if a +.Ar file +operand is +.Sq Fl . +.El +.Pp +The +.Fl s +option does not allow the option letter to be separated from its +argument, and the options +.Fl e , +.Fl i , +and +.Fl n +require that both arguments, if present, not be separated from the option +letter. +.Sh ERRORS +If +.Nm +receives an interrupt while printing to a terminal, it +flushes all accumulated error messages to the screen before +terminating. +.Pp +The +.Nm +utility exits 0 on success, and 1 if an error occurs. +.Pp +Error messages are written to standard error during the printing +process (if output is redirected) or after all successful +file printing is complete (when printing to a terminal). +.Sh SEE ALSO +.Xr cat 1 , +.Xr more 1 , +.Xr strftime 3 +.Sh STANDARDS +The +.Nm +utility is +.St -p1003.1-2008 +compatible. diff --git a/usr.bin/pr/pr.c b/usr.bin/pr/pr.c new file mode 100644 index 0000000..91e8075 --- /dev/null +++ b/usr.bin/pr/pr.c @@ -0,0 +1,1901 @@ +/* $NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $ */ + +/*- + * Copyright (c) 1991 Keith Muller. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2012 + * The NetBSD Foundation, Inc. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +from: static char sccsid[] = "@(#)pr.c 8.1 (Berkeley) 6/6/93"; +#else +__RCSID("$NetBSD: pr.c,v 1.24 2012/08/01 02:27:48 ginsbach Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pr.h" +#include "extern.h" + +/* + * pr: a printing and pagination filter. If multiple input files + * are specified, each is read, formatted, and written to standard + * output. By default, input is separated into 66-line pages, each + * with a header that includes the page number, date, time and the + * files pathname. + * + * Complies with posix P1003.2/D11 + */ + +/* + * parameter variables + */ +static int pgnm; /* starting page number */ +static int clcnt; /* number of columns */ +static int colwd; /* column data width - multiple columns */ +static int across; /* mult col flag; write across page */ +static int dspace; /* double space flag */ +static char inchar; /* expand input char */ +static int ingap; /* expand input gap */ +static int formfeed; /* use formfeed as trailer */ +static char *header; /* header name instead of file name */ +static char ochar; /* contract output char */ +static int ogap; /* contract output gap */ +static int lines; /* number of lines per page */ +static int merge; /* merge multiple files in output */ +static char nmchar; /* line numbering append char */ +static int nmwd; /* width of line number field */ +static int offst; /* number of page offset spaces */ +static int nodiag; /* do not report file open errors */ +static char schar; /* text column separation character */ +static int sflag; /* -s option for multiple columns */ +static int ttyout; /* output is a tty */ +static int nohead; /* do not write head and trailer */ +static int pgpause; /* pause before each page */ +static int pgwd; /* page width with multiple col output */ +static const char *timefrmt = TIMEFMT; /* time conversion string */ +static FILE *ttyinf; /* input terminal for page pauses */ + +/* + * misc globals + */ +static FILE *errf; /* error message file pointer */ +static int addone; /* page length is odd with double space */ +static int errcnt; /* error count on file processing */ +static const char digs[] = "0123456789"; /* page number translation map */ + +static void addnum(char *, int, int); +static void flsh_errs(void); +static int horzcol(int, char **); +static int inln(FILE *, char *, int, int *, int, int *); +static int inskip(FILE *, int, int); +static void mfail(void); +static int mulfile(int, char **); +static FILE *nxtfile(int, char **, const char **, char *, int); +static int onecol(int, char **); +static int otln(char *, int, int *, int *, int); +static void pfail(void); +static int prhead(char *, const char *, int); +static void prpause(int); +static int prtail(int, int); +static int setup(int, char **); +__dead static void terminate(int); +static void usage(void); +static int vertcol(int, char **); + +int +main(int argc, char *argv[]) +{ + int ret_val; + + if (signal(SIGINT, SIG_IGN) != SIG_IGN) + (void)signal(SIGINT, terminate); + ret_val = setup(argc, argv); + if (!ret_val) { + /* + * select the output format based on options + */ + if (merge) + ret_val = mulfile(argc, argv); + else if (clcnt == 1) + ret_val = onecol(argc, argv); + else if (across) + ret_val = horzcol(argc, argv); + else + ret_val = vertcol(argc, argv); + } else + usage(); + flsh_errs(); + if (errcnt || ret_val) + exit(1); + return(0); +} + +/* + * onecol: print files with only one column of output. + * Line length is unlimited. + */ +static int +onecol(int argc, char *argv[]) +{ + int cnt = -1; + int off; + int lrgln; + int linecnt; + int num; + int lncnt; + int pagecnt; + int ips; + int ops; + int cps; + char *obuf = NULL; + char *lbuf; + char *nbuf; + char *hbuf = NULL; + char *ohbuf; + FILE *inf = NULL; + const char *fname; + int mor; + int error = 1; + + if (nmwd) + num = nmwd + 1; + else + num = 0; + off = num + offst; + + /* + * allocate line buffer + */ + if ((obuf = malloc((unsigned)(LBUF + off)*sizeof(char))) == NULL) + goto oomem; + /* + * allocate header buffer + */ + if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) + goto oomem; + + ohbuf = hbuf + offst; + nbuf = obuf + offst; + lbuf = nbuf + num; + if (num) + nbuf[--num] = nmchar; + if (offst) { + (void)memset(obuf, (int)' ', offst); + (void)memset(hbuf, (int)' ', offst); + } + + /* + * loop by file + */ + while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) { + if (pgnm) { + /* + * skip to specified page + */ + if (inskip(inf, pgnm, lines)) + continue; + pagecnt = pgnm; + } else + pagecnt = 1; + lncnt = 0; + + /* + * loop by page + */ + for(;;) { + linecnt = 0; + lrgln = 0; + ops = 0; + ips = 0; + cps = 0; + + /* + * loop by line + */ + while (linecnt < lines) { + /* + * input next line + */ + if ((cnt = inln(inf,lbuf,LBUF,&cps,0,&mor)) < 0) + break; + if (!linecnt) { + if (pgpause) + prpause(pagecnt); + + if (!nohead && + prhead(hbuf, fname, pagecnt)) + goto out; + } + + /* + * start of new line. + */ + if (!lrgln) { + if (num) + addnum(nbuf, num, ++lncnt); + if (otln(obuf,cnt+off, &ips, &ops, mor)) + goto out; + } else if (otln(lbuf, cnt, &ips, &ops, mor)) + goto out; + + /* + * if line bigger than buffer, get more + */ + if (mor) { + lrgln = 1; + continue; + } + + /* + * whole line rcvd. reset tab proc. state + */ + ++linecnt; + lrgln = 0; + ops = 0; + ips = 0; + } + + /* + * fill to end of page + */ + if (linecnt && prtail(lines-linecnt-lrgln, lrgln)) + goto out; + + /* + * On EOF go to next file + */ + if (cnt < 0) + break; + ++pagecnt; + } + if (inf != stdin) + (void)fclose(inf); + } + if (eoptind < argc) + goto out; + error = 0; + goto out; +oomem: + mfail(); +out: + free(obuf); + free(hbuf); + if (inf != NULL && inf != stdin) + (void)fclose(inf); + return error; +} + +/* + * vertcol: print files with more than one column of output down a page + */ +static int +vertcol(int argc, char *argv[]) +{ + char *ptbf; + char **lstdat = NULL; + int i; + int j; + int cnt = -1; + int pln; + int *indy = NULL; + int cvc; + int *lindy = NULL; + int lncnt; + int stp; + int pagecnt; + int col = colwd + 1; + int mxlen = pgwd + offst + 1; + int mclcnt = clcnt - 1; + struct vcol *vc = NULL; + int mvc; + int tvc; + int cw = nmwd + 1; + int fullcol; + char *buf = NULL; + char *hbuf = NULL; + char *ohbuf; + const char *fname; + FILE *inf = NULL; + int ips = 0; + int cps = 0; + int ops = 0; + int mor = 0; + int error = 1; + + /* + * allocate page buffer + */ + if ((buf = malloc((unsigned)lines*mxlen*sizeof(char))) == NULL) + goto oomem; + + /* + * allocate page header + */ + if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) + goto oomem; + ohbuf = hbuf + offst; + if (offst) + (void)memset(hbuf, (int)' ', offst); + + /* + * col pointers when no headers + */ + mvc = lines * clcnt; + if ((vc = malloc((unsigned)mvc*sizeof(struct vcol))) == NULL) + goto oomem; + + /* + * pointer into page where last data per line is located + */ + if ((lstdat = malloc((unsigned)lines*sizeof(char *))) == NULL) + goto oomem; + + /* + * fast index lookups to locate start of lines + */ + if ((indy = malloc((unsigned)lines*sizeof(int))) == NULL) + goto oomem; + if ((lindy = malloc((unsigned)lines*sizeof(int))) == NULL) + goto oomem; + + if (nmwd) + fullcol = col + cw; + else + fullcol = col; + + /* + * initialize buffer lookup indexes and offset area + */ + for (j = 0; j < lines; ++j) { + lindy[j] = j * mxlen; + indy[j] = lindy[j] + offst; + if (offst) { + ptbf = buf + lindy[j]; + (void)memset(ptbf, (int)' ', offst); + ptbf += offst; + } else + ptbf = buf + indy[j]; + lstdat[j] = ptbf; + } + + /* + * loop by file + */ + while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) { + if (pgnm) { + /* + * skip to requested page + */ + if (inskip(inf, pgnm, lines)) + continue; + pagecnt = pgnm; + } else + pagecnt = 1; + lncnt = 0; + + /* + * loop by page + */ + for(;;) { + /* + * loop by column + */ + cvc = 0; + for (i = 0; i < clcnt; ++i) { + j = 0; + /* + * if last column, do not pad + */ + if (i == mclcnt) + stp = 1; + else + stp = 0; + /* + * loop by line + */ + for(;;) { + /* + * is this first column + */ + if (!i) { + ptbf = buf + indy[j]; + lstdat[j] = ptbf; + } else + ptbf = lstdat[j]; + vc[cvc].pt = ptbf; + + /* + * add number + */ + if (nmwd) { + addnum(ptbf, nmwd, ++lncnt); + ptbf += nmwd; + *ptbf++ = nmchar; + } + + /* + * input next line + */ + cnt = inln(inf,ptbf,colwd,&cps,1,&mor); + vc[cvc++].cnt = cnt; + if (cnt < 0) + break; + ptbf += cnt; + + /* + * pad all but last column on page + */ + if (!stp) { + /* + * pad to end of column + */ + if (sflag) + *ptbf++ = schar; + else if ((pln = col-cnt) > 0) { + (void)memset(ptbf, + (int)' ',pln); + ptbf += pln; + } + } + /* + * remember last char in line + */ + lstdat[j] = ptbf; + if (++j >= lines) + break; + } + if (cnt < 0) + break; + } + + /* + * when -t (no header) is specified the spec requires + * the min number of lines. The last page may not have + * balanced length columns. To fix this we must reorder + * the columns. This is a very slow technique so it is + * only used under limited conditions. Without -t, the + * balancing of text columns is unspecified. To NOT + * balance the last page, add the global variable + * nohead to the if statement below e.g. + * + * if ((cnt < 0) && nohead && cvc ...... + */ + --cvc; + + /* + * check to see if last page needs to be reordered + */ + if ((cnt < 0) && cvc && ((mvc-cvc) >= clcnt)){ + pln = cvc/clcnt; + if (cvc % clcnt) + ++pln; + + if (pgpause) + prpause(pagecnt); + + /* + * print header + */ + if (!nohead && prhead(hbuf, fname, pagecnt)) + goto out; + for (i = 0; i < pln; ++i) { + ips = 0; + ops = 0; + if (offst&& otln(buf,offst,&ips,&ops,1)) { + error = 1; + goto out; + } + tvc = i; + + for (j = 0; j < clcnt; ++j) { + /* + * determine column length + */ + if (j == mclcnt) { + /* + * last column + */ + cnt = vc[tvc].cnt; + if (nmwd) + cnt += cw; + } else if (sflag) { + /* + * single ch between + */ + cnt = vc[tvc].cnt + 1; + if (nmwd) + cnt += cw; + } else + cnt = fullcol; + if (otln(vc[tvc].pt, cnt, &ips, + &ops, 1)) + goto out; + tvc += pln; + if (tvc >= cvc) + break; + } + /* + * terminate line + */ + if (otln(buf, 0, &ips, &ops, 0)) + goto out; + } + /* + * pad to end of page + */ + if (prtail((lines - pln), 0)) + goto out; + /* + * done with output, go to next file + */ + break; + } + + /* + * determine how many lines to output + */ + if (i > 0) + pln = lines; + else + pln = j; + + /* + * print header + */ + if (pln) { + if (pgpause) + prpause(pagecnt); + + if (!nohead && prhead(hbuf, fname, pagecnt)) + goto out; + } + + /* + * output each line + */ + for (i = 0; i < pln; ++i) { + ptbf = buf + lindy[i]; + if ((j = lstdat[i] - ptbf) <= offst) + break; + if (otln(ptbf, j, &ips, &ops, 0)) + goto out; + } + + /* + * pad to end of page + */ + if (pln && prtail((lines - pln), 0)) + goto out; + + /* + * if EOF go to next file + */ + if (cnt < 0) + break; + ++pagecnt; + } + if (inf != stdin) + (void)fclose(inf); + } + if (eoptind < argc) + goto out; + error = 0; + goto out; +oomem: + mfail(); +out: + free(buf); + free(hbuf); + free(vc); + free(lstdat); + free(lindy); + if (inf != NULL && inf != stdin) + (void)fclose(inf); + return error; +} + +/* + * horzcol: print files with more than one column of output across a page + */ +static int +horzcol(int argc, char *argv[]) +{ + char *ptbf; + int pln; + int cnt = -1; + char *lstdat; + int col = colwd + 1; + int j; + int i; + int lncnt; + int pagecnt; + char *buf = NULL; + char *hbuf = NULL; + char *ohbuf; + const char *fname; + FILE *inf = NULL; + int ips = 0; + int cps = 0; + int ops = 0; + int mor = 0; + int error = 1; + + if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) + goto oomem; + + /* + * page header + */ + if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) + goto oomem; + ohbuf = hbuf + offst; + if (offst) { + (void)memset(buf, (int)' ', offst); + (void)memset(hbuf, (int)' ', offst); + } + + /* + * loop by file + */ + while ((inf = nxtfile(argc, argv, &fname, ohbuf, 0)) != NULL) { + if (pgnm) { + if (inskip(inf, pgnm, lines)) + continue; + pagecnt = pgnm; + } else + pagecnt = 1; + lncnt = 0; + + /* + * loop by page + */ + for(;;) { + /* + * loop by line + */ + for (i = 0; i < lines; ++i) { + ptbf = buf + offst; + lstdat = ptbf; + j = 0; + /* + * loop by col + */ + for(;;) { + if (nmwd) { + /* + * add number to column + */ + addnum(ptbf, nmwd, ++lncnt); + ptbf += nmwd; + *ptbf++ = nmchar; + } + /* + * input line + */ + if ((cnt = inln(inf,ptbf,colwd,&cps,1, + &mor)) < 0) + break; + ptbf += cnt; + lstdat = ptbf; + + /* + * if last line skip padding + */ + if (++j >= clcnt) + break; + + /* + * pad to end of column + */ + if (sflag) + *ptbf++ = schar; + else if ((pln = col - cnt) > 0) { + (void)memset(ptbf,(int)' ',pln); + ptbf += pln; + } + } + + /* + * determine line length + */ + if ((j = lstdat - buf) <= offst) + break; + if (!i) { + if (pgpause) + prpause(pagecnt); + + if (!nohead && + prhead(hbuf, fname, pagecnt)) + goto out; + } + /* + * output line + */ + if (otln(buf, j, &ips, &ops, 0)) + goto out; + } + + /* + * pad to end of page + */ + if (i && prtail(lines-i, 0)) + goto out; + + /* + * if EOF go to next file + */ + if (cnt < 0) + break; + ++pagecnt; + } + if (inf != stdin) + (void)fclose(inf); + } + if (eoptind < argc) + goto out; + error = 0; + goto out; +oomem: + mfail(); +out: + free(buf); + free(hbuf); + if (inf != NULL && inf != stdin) + (void)fclose(inf); + return error; +} + +/* + * mulfile: print files with more than one column of output and + * more than one file concurrently + */ +static int +mulfile(int argc, char *argv[]) +{ + char *ptbf; + int j; + int pln; + int cnt; + char *lstdat; + int i; + FILE **fbuf = NULL; + int actf; + int lncnt; + int col; + int pagecnt; + int fproc; + char *buf = NULL; + char *hbuf = NULL; + char *ohbuf; + const char *fname; + int ips = 0; + int cps = 0; + int ops = 0; + int mor = 0; + int error = 1; + + /* + * array of FILE *, one for each operand + */ + if ((fbuf = calloc(clcnt, sizeof(FILE *))) == NULL) + goto oomem; + + /* + * page header + */ + if ((hbuf = malloc((unsigned)(HDBUF + offst)*sizeof(char))) == NULL) + goto oomem; + ohbuf = hbuf + offst; + + /* + * do not know how many columns yet. The number of operands provide an + * upper bound on the number of columns. We use the number of files + * we can open successfully to set the number of columns. The operation + * of the merge operation (-m) in relation to unsuccesful file opens + * is unspecified by posix. + */ + j = 0; + while (j < clcnt) { + if ((fbuf[j] = nxtfile(argc, argv, &fname, ohbuf, 1)) == NULL) + break; + if (pgnm && (inskip(fbuf[j], pgnm, lines))) + fbuf[j] = NULL; + ++j; + } + + /* + * if no files, exit + */ + if (!j) + goto out; + + /* + * calculate page boundries based on open file count + */ + clcnt = j; + if (nmwd) { + colwd = (pgwd - clcnt - nmwd)/clcnt; + pgwd = ((colwd + 1) * clcnt) - nmwd - 2; + } else { + colwd = (pgwd + 1 - clcnt)/clcnt; + pgwd = ((colwd + 1) * clcnt) - 1; + } + if (colwd < 1) { + (void)fprintf(errf, + "pr: page width too small for %d columns\n", clcnt); + goto out; + } + actf = clcnt; + col = colwd + 1; + + /* + * line buffer + */ + if ((buf = malloc((unsigned)(pgwd+offst+1)*sizeof(char))) == NULL) + goto out; + if (offst) { + (void)memset(buf, (int)' ', offst); + (void)memset(hbuf, (int)' ', offst); + } + if (pgnm) + pagecnt = pgnm; + else + pagecnt = 1; + lncnt = 0; + + /* + * continue to loop while any file still has data + */ + while (actf > 0) { + /* + * loop by line + */ + for (i = 0; i < lines; ++i) { + ptbf = buf + offst; + lstdat = ptbf; + if (nmwd) { + /* + * add line number to line + */ + addnum(ptbf, nmwd, ++lncnt); + ptbf += nmwd; + *ptbf++ = nmchar; + } + j = 0; + fproc = 0; + + /* + * loop by column + */ + for (j = 0; j < clcnt; ++j) { + if (fbuf[j] == NULL) { + /* + * empty column; EOF + */ + cnt = 0; + } else if ((cnt = inln(fbuf[j], ptbf, colwd, + &cps, 1, &mor)) < 0) { + /* + * EOF hit; no data + */ + if (fbuf[j] != stdin) + (void)fclose(fbuf[j]); + fbuf[j] = NULL; + --actf; + cnt = 0; + } else { + /* + * process file data + */ + ptbf += cnt; + lstdat = ptbf; + fproc++; + } + + /* + * if last ACTIVE column, done with line + */ + if (fproc >= actf) + break; + + /* + * pad to end of column + */ + if (sflag) { + *ptbf++ = schar; + } else if ((pln = col - cnt) > 0) { + (void)memset(ptbf, (int)' ', pln); + ptbf += pln; + } + } + + /* + * calculate data in line + */ + if ((j = lstdat - buf) <= offst) + break; + + if (!i) { + if (pgpause) + prpause(pagecnt); + + if (!nohead && prhead(hbuf, fname, pagecnt)) + goto out; + } + + /* + * output line + */ + if (otln(buf, j, &ips, &ops, 0)) + goto out; + + /* + * if no more active files, done + */ + if (actf <= 0) { + ++i; + break; + } + } + + /* + * pad to end of page + */ + if (i && prtail(lines-i, 0)) + goto out; + ++pagecnt; + } + if (eoptind < argc) + goto out; + error = 0; + goto out; +oomem: + mfail(); +out: + if (fbuf) { + for (j = 0; j < clcnt; j++) + if (fbuf[j] && fbuf[j] != stdin) + (void)fclose(fbuf[j]); + free(fbuf); + } + free(hbuf); + free(buf); + return error; +} + +/* + * inln(): input a line of data (unlimited length lines supported) + * Input is optionally expanded to spaces + * + * inf: file + * buf: buffer + * lim: buffer length + * cps: column positon 1st char in buffer (large line support) + * trnc: throw away data more than lim up to \n + * mor: set if more data in line (not truncated) + */ +static int +inln(FILE *inf, char *buf, int lim, int *cps, int trnc, int *mor) +{ + int col; + int gap = ingap; + int ch = EOF; + char *ptbuf; + int chk = (int)inchar; + + ptbuf = buf; + + if (gap) { + /* + * expanding input option + */ + while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) { + /* + * is this the input "tab" char + */ + if (ch == chk) { + /* + * expand to number of spaces + */ + col = (ptbuf - buf) + *cps; + col = gap - (col % gap); + + /* + * if more than this line, push back + */ + if ((col > lim) && (ungetc(ch, inf) == EOF)) + return(1); + + /* + * expand to spaces + */ + while ((--col >= 0) && (--lim >= 0)) + *ptbuf++ = ' '; + continue; + } + if (ch == '\n') + break; + *ptbuf++ = ch; + } + } else { + /* + * no expansion + */ + while ((--lim >= 0) && ((ch = getc(inf)) != EOF)) { + if (ch == '\n') + break; + *ptbuf++ = ch; + } + } + col = ptbuf - buf; + if (ch == EOF) { + *mor = 0; + *cps = 0; + if (!col) + return(-1); + return(col); + } + if (ch == '\n') { + /* + * entire line processed + */ + *mor = 0; + *cps = 0; + return(col); + } + + /* + * line was larger than limit + */ + if (trnc) { + /* + * throw away rest of line + */ + while ((ch = getc(inf)) != EOF) { + if (ch == '\n') + break; + } + *cps = 0; + *mor = 0; + } else { + /* + * save column offset if not truncated + */ + *cps += col; + *mor = 1; + } + + return(col); +} + +/* + * otln(): output a line of data. (Supports unlimited length lines) + * output is optionally contracted to tabs + * + * buf: output buffer with data + * cnt: number of chars of valid data in buf + * svips: buffer input column position (for large lines) + * svops: buffer output column position (for large lines) + * mor: output line not complete in this buf; more data to come. + * 1 is more, 0 is complete, -1 is no \n's + */ +static int +otln(char *buf, int cnt, int *svips, int *svops, int mor) +{ + int ops; /* last col output */ + int ips; /* last col in buf examined */ + int gap = ogap; + int tbps; + char *endbuf; + + if (ogap) { + /* + * contracting on output + */ + endbuf = buf + cnt; + ops = *svops; + ips = *svips; + while (buf < endbuf) { + /* + * count number of spaces and ochar in buffer + */ + if (*buf == ' ') { + ++ips; + ++buf; + continue; + } + + /* + * simulate ochar processing + */ + if (*buf == ochar) { + ips += gap - (ips % gap); + ++buf; + continue; + } + + /* + * got a non space char; contract out spaces + */ + while (ips - ops > 1) { + /* + * use as many ochar as will fit + */ + if ((tbps = ops + gap - (ops % gap)) > ips) + break; + if (putchar(ochar) == EOF) { + pfail(); + return(1); + } + ops = tbps; + } + + while (ops < ips) { + /* + * finish off with spaces + */ + if (putchar(' ') == EOF) { + pfail(); + return(1); + } + ++ops; + } + + /* + * output non space char + */ + if (putchar(*buf++) == EOF) { + pfail(); + return(1); + } + ++ips; + ++ops; + } + + if (mor > 0) { + /* + * if incomplete line, save position counts + */ + *svops = ops; + *svips = ips; + return(0); + } + + if (mor < 0) { + while (ips - ops > 1) { + /* + * use as many ochar as will fit + */ + if ((tbps = ops + gap - (ops % gap)) > ips) + break; + if (putchar(ochar) == EOF) { + pfail(); + return(1); + } + ops = tbps; + } + while (ops < ips) { + /* + * finish off with spaces + */ + if (putchar(' ') == EOF) { + pfail(); + return(1); + } + ++ops; + } + return(0); + } + } else { + /* + * output is not contracted + */ + if (cnt && (fwrite(buf, sizeof(char), cnt, stdout) <= 0)) { + pfail(); + return(1); + } + if (mor != 0) + return(0); + } + + /* + * process line end and double space as required + */ + if ((putchar('\n') == EOF) || (dspace && (putchar('\n') == EOF))) { + pfail(); + return(1); + } + return(0); +} + +/* + * inskip(): skip over pgcnt pages with lncnt lines per page + * file is closed at EOF (if not stdin). + * + * inf FILE * to read from + * pgcnt number of pages to skip + * lncnt number of lines per page + */ +static int +inskip(FILE *inf, int pgcnt, int lncnt) +{ + int c; + int cnt; + + while(--pgcnt > 0) { + cnt = lncnt; + while ((c = getc(inf)) != EOF) { + if ((c == '\n') && (--cnt == 0)) + break; + } + if (c == EOF) { + if (inf != stdin) + (void)fclose(inf); + return(1); + } + } + return(0); +} + +/* + * nxtfile: returns a FILE * to next file in arg list and sets the + * time field for this file (or current date). + * + * buf array to store proper date for the header. + * dt if set skips the date processing (used with -m) + */ +static FILE * +nxtfile(int argc, char **argv, const char **fname, char *buf, int dt) +{ + FILE *inf = NULL; + struct timeval tv; + struct timezone tz; + struct tm *timeptr = NULL; + struct stat statbuf; + time_t curtime; + static int twice = -1; + + ++twice; + if (eoptind >= argc) { + /* + * no file listed; default, use standard input + */ + if (twice) + return(NULL); + clearerr(stdin); + inf = stdin; + if (header != NULL) + *fname = header; + else + *fname = FNAME; + if (nohead) + return(inf); + if (gettimeofday(&tv, &tz) < 0) { + ++errcnt; + (void)fprintf(errf, "pr: cannot get time of day, %s\n", + strerror(errno)); + eoptind = argc - 1; + return(NULL); + } + curtime = tv.tv_sec; + timeptr = localtime(&curtime); + } + for (; eoptind < argc; ++eoptind) { + if (strcmp(argv[eoptind], "-") == 0) { + /* + * process a "-" for filename + */ + clearerr(stdin); + inf = stdin; + if (header != NULL) + *fname = header; + else + *fname = FNAME; + ++eoptind; + if (nohead || (dt && twice)) + return(inf); + if (gettimeofday(&tv, &tz) < 0) { + ++errcnt; + (void)fprintf(errf, + "pr: cannot get time of day, %s\n", + strerror(errno)); + return(NULL); + } + curtime = tv.tv_sec; + timeptr = localtime(&curtime); + } else { + /* + * normal file processing + */ + if ((inf = fopen(argv[eoptind], "r")) == NULL) { + ++errcnt; + if (nodiag) + continue; + (void)fprintf(errf, "pr: Cannot open %s, %s\n", + argv[eoptind], strerror(errno)); + continue; + } + if (header != NULL) + *fname = header; + else if (dt) + *fname = FNAME; + else + *fname = argv[eoptind]; + ++eoptind; + if (nohead || (dt && twice)) + return(inf); + + if (dt) { + if (gettimeofday(&tv, &tz) < 0) { + ++errcnt; + (void)fprintf(errf, + "pr: cannot get time of day, %s\n", + strerror(errno)); + return(NULL); + } + curtime = tv.tv_sec; + timeptr = localtime(&curtime); + } else { + if (fstat(fileno(inf), &statbuf) < 0) { + ++errcnt; + (void)fclose(inf); + (void)fprintf(errf, + "pr: Cannot stat %s, %s\n", + argv[eoptind], strerror(errno)); + return(NULL); + } + timeptr = localtime(&(statbuf.st_mtime)); + } + } + break; + } + if (inf == NULL) + return(NULL); + + /* + * set up time field used in header + */ + if (strftime(buf, HDBUF, timefrmt, timeptr) <= 0) { + ++errcnt; + if (inf != stdin) + (void)fclose(inf); + (void)fputs("pr: time conversion failed\n", errf); + return(NULL); + } + return(inf); +} + +/* + * addnum(): adds the line number to the column + * Truncates from the front or pads with spaces as required. + * Numbers are right justified. + * + * buf buffer to store the number + * wdth width of buffer to fill + * line line number + * + * NOTE: numbers occupy part of the column. The posix + * spec does not specify if -i processing should or should not + * occur on number padding. The spec does say it occupies + * part of the column. The usage of addnum currently treats + * numbers as part of the column so spaces may be replaced. + */ +void +addnum(char *buf, int wdth, int line) +{ + char *pt = buf + wdth; + + do { + *--pt = digs[line % 10]; + line /= 10; + } while (line && (pt > buf)); + + /* + * pad with space as required + */ + while (pt > buf) + *--pt = ' '; +} + +/* + * prpause(): pause before printing each page + * + * pagcnt page number + */ +static void +prpause(int pagcnt) +{ + + if (ttyout) { + int c; + + (void)putc('\a', stderr); + (void)fflush(stderr); + + while ((c = getc(ttyinf)) != '\n' && c != EOF) + ; + + /* + * pause ONLY before first page of first file + */ + if (pgpause == FIRSTPAGE && pagcnt == 1) + pgpause = NO_PAUSE; + } +} + +/* + * prhead(): prints the top of page header + * + * buf buffer with time field (and offset) + * cnt number of chars in buf + * fname fname field for header + * pagcnt page number + */ +static int +prhead(char *buf, const char *fname, int pagcnt) +{ + int ips = 0; + int ops = 0; + + if ((putchar('\n') == EOF) || (putchar('\n') == EOF)) { + pfail(); + return(1); + } + /* + * posix is not clear if the header is subject to line length + * restrictions. The specification for header line format + * in the spec clearly does not limit length. No pr currently + * restricts header length. However if we need to truncate in + * an reasonable way, adjust the length of the printf by + * changing HDFMT to allow a length max as an argument printf. + * buf (which contains the offset spaces and time field could + * also be trimmed + * + * note only the offset (if any) is processed for tab expansion + */ + if (offst && otln(buf, offst, &ips, &ops, -1)) + return(1); + (void)printf(HDFMT,buf+offst, fname, pagcnt); + return(0); +} + +/* + * prtail(): pad page with empty lines (if required) and print page trailer + * if requested + * + * cnt number of lines of padding needed + * incomp was a '\n' missing from last line output + */ +static int +prtail(int cnt, int incomp) +{ + if (nohead) { + /* + * only pad with no headers when incomplete last line + */ + if (!incomp) + return(0); + if ((dspace && (putchar('\n') == EOF)) || + (putchar('\n') == EOF)) { + pfail(); + return(1); + } + return(0); + } + + /* + * if double space output two \n + */ + if (dspace) + cnt *= 2; + + /* + * if an odd number of lines per page, add an extra \n + */ + if (addone) + ++cnt; + + /* + * pad page + */ + if (formfeed) { + if ((incomp && (putchar('\n') == EOF)) || + (putchar('\f') == EOF)) { + pfail(); + return(1); + } + return(0); + } + cnt += TAILLEN; + while (--cnt >= 0) { + if (putchar('\n') == EOF) { + pfail(); + return(1); + } + } + return(0); +} + +/* + * terminate(): when a SIGINT is recvd + */ +static void +terminate(int which_sig) +{ + flsh_errs(); + (void)raise_default_signal(which_sig); + exit(1); +} + + +/* + * flsh_errs(): output saved up diagnostic messages after all normal + * processing has completed + */ +static void +flsh_errs(void) +{ + char buf[BUFSIZ]; + + (void)fflush(stdout); + (void)fflush(errf); + if (errf == stderr) + return; + rewind(errf); + while (fgets(buf, BUFSIZ, errf) != NULL) + (void)fputs(buf, stderr); +} + +static void +mfail(void) +{ + (void)fputs("pr: memory allocation failed\n", errf); +} + +static void +pfail(void) +{ + (void)fprintf(errf, "pr: write failure, %s\n", strerror(errno)); +} + +static void +usage(void) +{ + (void)fputs( + "usage: pr [+page] [-col] [-adFfmprt] [-e[ch][gap]] [-h header]\n", + errf); + (void)fputs( + " [-i[ch][gap]] [-l line] [-n[ch][width]] [-o offset]\n", + errf); + (void)fputs( + " [-s[ch]] [-T timefmt] [-w width] [-] [file ...]\n", + errf); +} + +/* + * setup: Validate command args, initialize and perform sanity + * checks on options + */ +static int +setup(int argc, char **argv) +{ + int c; + int eflag = 0; + int iflag = 0; + int wflag = 0; + int cflag = 0; + + ttyinf = stdin; + + if (isatty(fileno(stdout))) { + /* + * defer diagnostics until processing is done + */ + if ((errf = tmpfile()) == NULL) { + (void)fputs("Cannot defer diagnostic messages\n",stderr); + return(1); + } + ttyout = 1; + } else + errf = stderr; + while ((c = egetopt(argc, argv, "#adFfmrte?h:i?l:n?o:ps?T:w:")) != -1) { + switch (c) { + case '+': + if ((pgnm = atoi(eoptarg)) < 1) { + (void)fputs("pr: +page number must be 1 or more\n", + errf); + return(1); + } + break; + case '-': + if ((clcnt = atoi(eoptarg)) < 1) { + (void)fputs("pr: -columns must be 1 or more\n",errf); + return(1); + } + if (clcnt > 1) + ++cflag; + break; + case 'a': + ++across; + break; + case 'd': + ++dspace; + break; + case 'e': + ++eflag; + if ((eoptarg != NULL) && + !isdigit((unsigned char)*eoptarg)) + inchar = *eoptarg++; + else + inchar = INCHAR; + if ((eoptarg != NULL) && + isdigit((unsigned char)*eoptarg)) { + if ((ingap = atoi(eoptarg)) < 0) { + (void)fputs( + "pr: -e gap must be 0 or more\n", errf); + return(1); + } + if (ingap == 0) + ingap = INGAP; + } else if ((eoptarg != NULL) && (*eoptarg != '\0')) { + (void)fprintf(errf, + "pr: invalid value for -e %s\n", eoptarg); + return(1); + } else + ingap = INGAP; + break; + case 'f': + pgpause |= FIRSTPAGE; + /*FALLTHROUGH*/ + case 'F': + ++formfeed; + break; + case 'h': + header = eoptarg; + break; + case 'i': + ++iflag; + if ((eoptarg != NULL) && + !isdigit((unsigned char)*eoptarg)) + ochar = *eoptarg++; + else + ochar = OCHAR; + if ((eoptarg != NULL) && + isdigit((unsigned char)*eoptarg)) { + if ((ogap = atoi(eoptarg)) < 0) { + (void)fputs( + "pr: -i gap must be 0 or more\n", errf); + return(1); + } + if (ogap == 0) + ogap = OGAP; + } else if ((eoptarg != NULL) && (*eoptarg != '\0')) { + (void)fprintf(errf, + "pr: invalid value for -i %s\n", eoptarg); + return(1); + } else + ogap = OGAP; + break; + case 'l': + if (!isdigit((unsigned char)*eoptarg) || + ((lines=atoi(eoptarg)) < 1)) { + (void)fputs( + "pr: Number of lines must be 1 or more\n",errf); + return(1); + } + break; + case 'm': + ++merge; + break; + case 'n': + if ((eoptarg != NULL) && + !isdigit((unsigned char)*eoptarg)) + nmchar = *eoptarg++; + else + nmchar = NMCHAR; + if ((eoptarg != NULL) && + isdigit((unsigned char)*eoptarg)) { + if ((nmwd = atoi(eoptarg)) < 1) { + (void)fputs( + "pr: -n width must be 1 or more\n",errf); + return(1); + } + } else if ((eoptarg != NULL) && (*eoptarg != '\0')) { + (void)fprintf(errf, + "pr: invalid value for -n %s\n", eoptarg); + return(1); + } else + nmwd = NMWD; + break; + case 'o': + if (!isdigit((unsigned char)*eoptarg) || + ((offst = atoi(eoptarg))< 1)){ + (void)fputs("pr: -o offset must be 1 or more\n", + errf); + return(1); + } + break; + case 'p': + pgpause |= EACHPAGE; + break; + case 'r': + ++nodiag; + break; + case 's': + ++sflag; + if (eoptarg == NULL) + schar = SCHAR; + else + schar = *eoptarg++; + if (eoptarg && *eoptarg != '\0') { + (void)fprintf(errf, + "pr: invalid value for -s %s\n", eoptarg); + return(1); + } + break; + case 'T': + timefrmt = eoptarg; + break; + case 't': + ++nohead; + break; + case 'w': + ++wflag; + if (!isdigit((unsigned char)*eoptarg) || + ((pgwd = atoi(eoptarg)) < 1)){ + (void)fputs( + "pr: -w width must be 1 or more \n",errf); + return(1); + } + break; + case '?': + default: + return(1); + } + } + + /* + * default and sanity checks + */ + if (!clcnt) { + if (merge) { + if ((clcnt = argc - eoptind) <= 1) { + clcnt = CLCNT; + merge = 0; + } + } else + clcnt = CLCNT; + } + if (across) { + if (clcnt == 1) { + (void)fputs("pr: -a flag requires multiple columns\n", + errf); + return(1); + } + if (merge) { + (void)fputs("pr: -m cannot be used with -a\n", errf); + return(1); + } + } + if (!wflag) { + if (sflag) + pgwd = SPGWD; + else + pgwd = PGWD; + } + if (cflag || merge) { + if (!eflag) { + inchar = INCHAR; + ingap = INGAP; + } + if (!iflag) { + ochar = OCHAR; + ogap = OGAP; + } + } + if (cflag) { + if (merge) { + (void)fputs( + "pr: -m cannot be used with multiple columns\n", errf); + return(1); + } + if (nmwd) { + colwd = (pgwd + 1 - (clcnt * (nmwd + 2)))/clcnt; + pgwd = ((colwd + nmwd + 2) * clcnt) - 1; + } else { + colwd = (pgwd + 1 - clcnt)/clcnt; + pgwd = ((colwd + 1) * clcnt) - 1; + } + if (colwd < 1) { + (void)fprintf(errf, + "pr: page width is too small for %d columns\n",clcnt); + return(1); + } + } + if (!lines) + lines = LINES; + + /* + * make sure long enough for headers. if not disable + */ + if (lines <= HEADLEN + TAILLEN) + ++nohead; + else if (!nohead) + lines -= HEADLEN + TAILLEN; + + /* + * adjust for double space on odd length pages + */ + if (dspace) { + if (lines == 1) + dspace = 0; + else { + if (lines & 1) + ++addone; + lines /= 2; + } + } + + /* + * open /dev/tty if we are to pause before each page + * but only if stdout is a terminal and stdin is not a terminal + */ + if (ttyout && pgpause && !isatty(fileno(stdin))) { + if ((ttyinf = fopen("/dev/tty", "r")) == NULL) { + (void)fprintf(errf, "pr: cannot open terminal\n"); + return(1); + } + } + + return(0); +} diff --git a/usr.bin/pr/pr.h b/usr.bin/pr/pr.h new file mode 100644 index 0000000..0afd374 --- /dev/null +++ b/usr.bin/pr/pr.h @@ -0,0 +1,79 @@ +/* $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $ */ + +/*- + * Copyright (c) 1991 Keith Muller. + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * Copyright (c) 2012 + * The NetBSD Foundation, Inc. + * + * This code is derived from software contributed to Berkeley by + * Keith Muller of the University of California, San Diego. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pr.h 8.1 (Berkeley) 6/6/93 + * $NetBSD: pr.h,v 1.5 2012/07/24 02:13:04 ginsbach Exp $ + */ + +/* + * parameter defaults + */ +#define CLCNT 1 +#define INCHAR '\t' +#define INGAP 8 +#define OCHAR '\t' +#define OGAP 8 +#define LINES 66 +#define NMWD 5 +#define NMCHAR '\t' +#define SCHAR '\t' +#define PGWD 72 +#define SPGWD 512 + +/* + * misc default values + */ +#define HDFMT "%s %s Page %d\n\n\n" +#define HEADLEN 5 +#define TAILLEN 5 +#define TIMEFMT "%b %e %H:%M %Y" +#define FNAME "" +#define LBUF 8192 +#define HDBUF 512 + +/* when to pause before (for -f and -p options) */ +#define NO_PAUSE 0 +#define FIRSTPAGE 1 +#define ENSUINGPAGES 2 +#define EACHPAGE (FIRSTPAGE | ENSUINGPAGES) + +/* + * structure for vertical columns. Used to balance cols on last page + */ +struct vcol { + char *pt; /* ptr to col */ + int cnt; /* char count */ +}; diff --git a/usr.bin/printf/printf.1 b/usr.bin/printf/printf.1 new file mode 100644 index 0000000..588614d --- /dev/null +++ b/usr.bin/printf/printf.1 @@ -0,0 +1,438 @@ +.\" $NetBSD: printf.1,v 1.31 2018/08/31 17:27:35 kre Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)printf.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd August 31, 2018 +.Dt PRINTF 1 +.Os +.Sh NAME +.Nm printf +.Nd formatted output +.Sh SYNOPSIS +.Nm +.Ar format +.Op Ar arguments ... +.Sh DESCRIPTION +.Nm +formats and prints its arguments, after the first, under control +of the +.Ar format . +The +.Ar format +is a character string which contains three types of objects: plain characters, +which are simply copied to standard output, character escape sequences which +are converted and copied to the standard output, and format specifications, +each of which causes printing of the next successive +.Ar argument . +.Pp +The +.Ar arguments +after the first are treated as strings if the corresponding format is +either +.Cm b , +.Cm B , +.Cm c , +or +.Cm s ; +otherwise it is evaluated as a C constant, with the following extensions: +.Pp +.Bl -bullet -offset indent -compact +.It +A leading plus or minus sign is allowed. +.It +If the leading character is a single or double quote, the value is the ASCII +code of the next character. +.El +.Pp +The format string is reused as often as necessary to satisfy the +.Ar arguments . +Any extra format specifications are evaluated with zero or the null +string. +.Pp +Character escape sequences are in backslash notation as defined in +.St -ansiC . +The characters and their meanings are as follows: +.Bl -tag -width Ds -offset indent +.It Cm \ee +Write an +.Aq escape +character. +.It Cm \ea +Write a +.Aq bell +character. +.It Cm \eb +Write a +.Aq backspace +character. +.It Cm \ef +Write a +.Aq form-feed +character. +.It Cm \en +Write a +.Aq new-line +character. +.It Cm \er +Write a +.Aq carriage return +character. +.It Cm \et +Write a +.Aq tab +character. +.It Cm \ev +Write a +.Aq vertical tab +character. +.It Cm \e\' +Write a +.Aq single quote +character. +.It Cm \e" +Write a +.Aq double quote +character. +.It Cm \e\e +Write a backslash character. +.It Cm \e Ns Ar num +Write an 8\-bit character whose ASCII +value is the 1\-, 2\-, or 3\-digit octal number +.Ar num . +.It Cm \ex Ns Ar xx +Write an 8\-bit character whose ASCII +value is the 1\- or 2\-digit hexadecimal number +.Ar xx . +.El +.Pp +Each format specification is introduced by the percent character +.Pq Dq \&% . +The remainder of the format specification includes, +in the following order: +.Bl -tag -width Ds +.It Zero or more of the following flags : +.Bl -tag -width Ds +.It Cm # +A +.Sq # +character specifying that the value should be printed in an +.Dq alternative form . +For +.Cm b , +.Cm c , +.Cm d , +and +.Cm s +formats, this option has no effect. +For the +.Cm o +format the precision of the number is increased to force the first +character of the output string to a zero. +For the +.Cm x +.Pq Cm X +format, a non-zero result has the string +.Li 0x +.Pq Li 0X +prepended to it. +For +.Cm e , +.Cm E , +.Cm f , +.Cm F , +.Cm g , +and +.Cm G +formats, the result will always contain a decimal point, even if no +digits follow the point (normally, a decimal point only appears in the +results of those formats if a digit follows the decimal point). +For +.Cm g +and +.Cm G +formats, trailing zeros are not removed from the result as they +would otherwise be. +.\" I turned this off - decided it isn't a valid use of '#' +.\" For the +.\" .Cm B +.\" format, backslash-escape sequences are expanded first; +.It Cm \&\- +A minus sign +.Sq \- +which specifies +.Em left adjustment +of the output in the indicated field; +.It Cm \&+ +A +.Sq \&+ +character specifying that there should always be +a sign placed before the number when using signed formats. +.It Sq \&\ \& +A space specifying that a blank should be left before a positive number +for a signed format. +A +.Sq \&+ +overrides a space if both are used; +.It Cm \&0 +A zero `0' character indicating that zero-padding should be used +rather than blank-padding. +A +.Sq \- +overrides a +.Sq \&0 +if both are used; +.El +.It Field Width : +An optional digit string specifying a +.Em field width ; +if the output string has fewer characters than the field width it will +be blank-padded on the left (or right, if the left-adjustment indicator +has been given) to make up the field width (note that a leading zero +is a flag, but an embedded zero is part of a field width); +.It Precision : +An optional period, +.Sq Cm \&. , +followed by an optional digit string giving a +.Em precision +which specifies the number of digits to appear after the decimal point, +for +.Cm e +and +.Cm f +formats, or the maximum number of characters to be printed +from a string +.Sm off +.Pf ( Cm b , +.Sm on +.Cm B , +and +.Cm s +formats); if the digit string is missing, the precision is treated +as zero; +.It Format : +A character which indicates the type of format to use (one of +.Cm diouxXfFeEgGaAbBcs ) . +.El +.Pp +A field width or precision may be +.Sq Cm \&* +instead of a digit string. +In this case an +.Ar argument +supplies the field width or precision. +.Pp +The format characters and their meanings are: +.Bl -tag -width Fl +.It Cm diouXx +The +.Ar argument , +which must represent an integer constant, +with an optional leading plus or minus sign, +is printed as a signed decimal (d or i), +unsigned octal (o), unsigned decimal (u), +or unsigned hexadecimal (X or x). +.It Cm fF +The +.Ar argument +is printed in the style +.Sm off +.Pf [\-]ddd Cm \&. No ddd +.Sm on +where the number of d's +after the decimal point is equal to the precision specification for +the argument. +If the precision is missing, 6 digits are given; if the precision +is explicitly 0, no digits and no decimal point are printed. +If the number is Infinity, or Not a Number (NaN), then +.Dq inf +.Pq \&or Dq nan +is printed for +.Cm f +format, and +.Dq INF +.Pq \&or Dq NAN +for +.Cm F +format. +.It Cm eE +The +.Ar argument +is printed in the style +.Sm off +.Pf [\-]d Cm \&. No ddd Cm e No \*(Pmdd +.Sm on +where there +is one digit before the decimal point and the number after is equal to +the precision specification for the argument; when the precision is +missing, 6 digits are produced. +An upper-case E is used for an +.Sq E +format, and upper-case for Infinity and NaN as for +.Sq F +format. +.It Cm gG +The +.Ar argument +is printed in style +.Cm f +.Pq Cm F +or in style +.Cm e +.Pq Cm E +whichever gives full precision in minimum space. +.It Cm aA +The +.Ar argument +is treated as a floating point number, +for which the underlying hexadecimal representation is +printed. +See +.Xr printf 3 +for the details. +.It Cm b +Characters from the string +.Ar argument +are printed with backslash-escape sequences expanded. +.Pp +The following additional backslash-escape sequences are supported: +.Bl -tag -width Ds +.It Cm \ec +Causes +.Nm +to ignore any remaining characters in the string operand containing it, +any remaining string operands, and any additional characters in +the format operand. +.It Cm \e0 Ns Ar num +Write an 8\-bit character whose ASCII value is the 1\-, 2\-, or +3\-digit octal number +.Ar num . +.It Cm \e^ Ns Ar c +Write the control character +.Ar c . +Generates characters `\e000' through `\e037`, and `\e177' (from `\e^?'). +.It Cm \eM\- Ns Ar c +Write the character +.Ar c +with the 8th bit set. +Generates characters `\e241' through `\e376`. +.It Cm \eM^ Ns Ar c +Write the control character +.Ar c +with the 8th bit set. +Generates characters `\e200' through `\e237`, and `\e377' (from `\eM^?'). +.El +.It Cm B +Characters from the string +.Ar argument +are printed with unprintable characters backslash-escaped using the +.Sm off +.Pf ` Cm \e Ar c No ', +.Pf ` Cm \e^ Ar c No ', +.Pf ` Cm \eM\- Ar c No ' +or +.Pf ` Cm \eM^ Ar c No ', +.Sm on +formats described above. +.It Cm c +The first character of +.Ar argument +is printed. +.It Cm s +Characters from the string +.Ar argument +are printed until the end is reached or until the number of characters +indicated by the precision specification is reached; if the +precision is omitted, all characters in the string are printed. +.It Cm \&% +Print a `%'; no argument is used. +.El +.Pp +In no case does a non-existent or small field width cause truncation of +a field; padding takes place only if the specified field width exceeds +the actual width. +.Pp +If the first character of +.Ar format +is a dash, +.Ar format +must be preceded by a word consisting of two dashes (--) to prevent it +from being interpreted as an option string. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr echo 1 , +.Xr printf 3 , +.Xr vis 3 , +.Xr printf 9 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2001 . +.Pp +Support for the floating point formats and `*' as a field width and precision +are optional in POSIX. +.Pp +The behaviour of the %B format and the \e', \e", \exxx, \ee and +\e[M][\-|^]c escape sequences are undefined in POSIX. +.Sh BUGS +Since the floating point numbers are translated from ASCII to +floating-point and then back again, floating-point precision may be lost. +.Pp +Hexadecimal character constants are restricted to, and should be specified +as, two character constants. +This is contrary to the ISO C standard but +does guarantee detection of the end of the constant. +.Sh NOTES +All formats which treat the +.Ar argument +as a number first convert the +.Ar argument +from its external representation as a character string +to an internal numeric representation, and then apply the +format to the internal numeric representation, producing +another external character string representation. +One might expect the +.Cm \&%c +format to do likewise, but in fact it does not. +.Pp +To convert a string representation of a decimal, octal, or hexadecimal +number into the corresponding character, two nested +.Nm +invocations may be used, in which the inner invocation +converts the input to an octal string, and the outer +invocation uses the octal string as part of a format. +For example, the following command outputs the character whose code +is 0x0A, which is a newline in ASCII: +.Pp +.Dl printf \&"$(printf \&"\e\e%o" \&"0x0A")" diff --git a/usr.bin/printf/printf.c b/usr.bin/printf/printf.c new file mode 100644 index 0000000..4d88e7d --- /dev/null +++ b/usr.bin/printf/printf.c @@ -0,0 +1,709 @@ +/* $NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if !defined(BUILTIN) && !defined(SHELL) +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif +#endif + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)printf.c 8.2 (Berkeley) 3/22/95"; +#else +__RCSID("$NetBSD: printf.c,v 1.48 2019/01/27 12:03:09 kre Exp $"); +#endif +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __GNUC__ +#define ESCAPE '\e' +#else +#define ESCAPE 033 +#endif + +static void conv_escape_str(char *, void (*)(int), int); +static char *conv_escape(char *, char *, int); +static char *conv_expand(const char *); +static char getchr(void); +static double getdouble(void); +static int getwidth(void); +static intmax_t getintmax(void); +static char *getstr(void); +static char *mklong(const char *, char); +static void check_conversion(const char *, const char *); +static void usage(void); + +static void b_count(int); +static void b_output(int); +static size_t b_length; +static char *b_fmt; + +static int rval; +static char **gargv; + +#ifdef BUILTIN /* csh builtin */ +#define main progprintf +#endif + +#ifdef SHELL /* sh (aka ash) builtin */ +#define main printfcmd +#include "../../bin/sh/bltin/bltin.h" +#endif /* SHELL */ + +#define PF(f, func) { \ + if (fieldwidth != -1) { \ + if (precision != -1) \ + error = printf(f, fieldwidth, precision, func); \ + else \ + error = printf(f, fieldwidth, func); \ + } else if (precision != -1) \ + error = printf(f, precision, func); \ + else \ + error = printf(f, func); \ +} + +#define APF(cpp, f, func) { \ + if (fieldwidth != -1) { \ + if (precision != -1) \ + error = asprintf(cpp, f, fieldwidth, precision, func); \ + else \ + error = asprintf(cpp, f, fieldwidth, func); \ + } else if (precision != -1) \ + error = asprintf(cpp, f, precision, func); \ + else \ + error = asprintf(cpp, f, func); \ +} + +#ifdef main +int main(int, char *[]); +#endif + +int +main(int argc, char *argv[]) +{ + char *fmt, *start; + int fieldwidth, precision; + char nextch; + char *format; + char ch; + int error, o; + +#if !defined(SHELL) && !defined(BUILTIN) + (void)setlocale (LC_ALL, ""); +#endif + + rval = 0; /* clear for builtin versions (avoid holdover) */ + + while ((o = getopt(argc, argv, "")) != -1) { + switch (o) { + case '?': + default: + usage(); + return 1; + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); + return 1; + } + + format = *argv; + gargv = ++argv; + +#define SKIP1 "#-+ 0'" +#define SKIP2 "0123456789" + do { + /* + * Basic algorithm is to scan the format string for conversion + * specifications -- once one is found, find out if the field + * width or precision is a '*'; if it is, gather up value. + * Note, format strings are reused as necessary to use up the + * provided arguments, arguments of zero/null string are + * provided to use up the format string. + */ + + /* find next format specification */ + for (fmt = format; (ch = *fmt++) != '\0';) { + if (ch == '\\') { + char c_ch; + fmt = conv_escape(fmt, &c_ch, 0); + putchar(c_ch); + continue; + } + if (ch != '%' || (*fmt == '%' && ++fmt)) { + (void)putchar(ch); + continue; + } + + /* + * Ok - we've found a format specification, + * Save its address for a later printf(). + */ + start = fmt - 1; + + /* skip to field width */ + fmt += strspn(fmt, SKIP1); + if (*fmt == '*') { + fmt++; + fieldwidth = getwidth(); + } else { + fieldwidth = -1; + + /* skip to possible '.' for precision */ + fmt += strspn(fmt, SKIP2); + } + + if (*fmt == '.') { + /* get following precision */ + fmt++; + if (*fmt == '*') { + fmt++; + precision = getwidth(); + } else { + precision = -1; + fmt += strspn(fmt, SKIP2); + } + } else + precision = -1; + + ch = *fmt; + if (!ch) { + warnx("%s: missing format character", start); + return 1; + } + + /* + * null terminate format string to we can use it + * as an argument to printf. + */ + nextch = fmt[1]; + fmt[1] = 0; + + switch (ch) { + + case 'B': { + const char *p = conv_expand(getstr()); + + if (p == NULL) + goto out; + *fmt = 's'; + PF(start, p); + if (error < 0) + goto out; + break; + } + case 'b': { + /* + * There has to be a better way to do this, + * but the string we generate might have + * embedded nulls + */ + static char *a, *t; + char *cp = getstr(); + + /* Free on entry in case shell longjumped out */ + if (a != NULL) + free(a); + a = NULL; + if (t != NULL) + free(t); + t = NULL; + + /* Count number of bytes we want to output */ + b_length = 0; + conv_escape_str(cp, b_count, 0); + t = malloc(b_length + 1); + if (t == NULL) + goto out; + (void)memset(t, 'x', b_length); + t[b_length] = 0; + + /* Get printf to calculate the lengths */ + *fmt = 's'; + APF(&a, start, t); + if (error == -1) + goto out; + b_fmt = a; + + /* Output leading spaces and data bytes */ + conv_escape_str(cp, b_output, 1); + + /* Add any trailing spaces */ + printf("%s", b_fmt); + break; + } + case 'c': { + char p = getchr(); + + PF(start, p); + if (error < 0) + goto out; + break; + } + case 's': { + char *p = getstr(); + + PF(start, p); + if (error < 0) + goto out; + break; + } + case 'd': + case 'i': { + intmax_t p = getintmax(); + char *f = mklong(start, ch); + + PF(f, p); + if (error < 0) + goto out; + break; + } + case 'o': + case 'u': + case 'x': + case 'X': { + uintmax_t p = (uintmax_t)getintmax(); + char *f = mklong(start, ch); + + PF(f, p); + if (error < 0) + goto out; + break; + } + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': { + double p = getdouble(); + + PF(start, p); + if (error < 0) + goto out; + break; + } + case '%': + /* Don't ask, but this is useful ... */ + if (fieldwidth == 'N' && precision == 'B') + return 0; + /* FALLTHROUGH */ + default: + warnx("%s: invalid directive", start); + return 1; + } + *fmt++ = ch; + *fmt = nextch; + /* escape if a \c was encountered */ + if (rval & 0x100) + return rval & ~0x100; + } + } while (gargv != argv && *gargv); + + return rval & ~0x100; + out: + warn("print failed"); + return 1; +} + +/* helper functions for conv_escape_str */ + +static void +/*ARGSUSED*/ +b_count(int ch) +{ + b_length++; +} + +/* Output one converted character for every 'x' in the 'format' */ + +static void +b_output(int ch) +{ + for (;;) { + switch (*b_fmt++) { + case 0: + b_fmt--; + return; + case ' ': + putchar(' '); + break; + default: + putchar(ch); + return; + } + } +} + + +/* + * Print SysV echo(1) style escape string + * Halts processing string if a \c escape is encountered. + */ +static void +conv_escape_str(char *str, void (*do_putchar)(int), int quiet) +{ + int value; + int ch; + char c; + + while ((ch = *str++) != '\0') { + if (ch != '\\') { + do_putchar(ch); + continue; + } + + ch = *str++; + if (ch == 'c') { + /* \c as in SYSV echo - abort all processing.... */ + rval |= 0x100; + break; + } + + /* + * %b string octal constants are not like those in C. + * They start with a \0, and are followed by 0, 1, 2, + * or 3 octal digits. + */ + if (ch == '0') { + int octnum = 0, i; + for (i = 0; i < 3; i++) { + if (!isdigit((unsigned char)*str) || *str > '7') + break; + octnum = (octnum << 3) | (*str++ - '0'); + } + do_putchar(octnum); + continue; + } + + /* \[M][^|-]C as defined by vis(3) */ + if (ch == 'M' && *str == '-') { + do_putchar(0200 | str[1]); + str += 2; + continue; + } + if (ch == 'M' && *str == '^') { + str++; + value = 0200; + ch = '^'; + } else + value = 0; + if (ch == '^') { + ch = *str++; + if (ch == '?') + value |= 0177; + else + value |= ch & 037; + do_putchar(value); + continue; + } + + /* Finally test for sequences valid in the format string */ + str = conv_escape(str - 1, &c, quiet); + do_putchar(c); + } +} + +/* + * Print "standard" escape characters + */ +static char * +conv_escape(char *str, char *conv_ch, int quiet) +{ + char value; + char ch; + char num_buf[4], *num_end; + + ch = *str++; + + switch (ch) { + case '\0': + if (!quiet) + warnx("incomplete escape sequence"); + rval = 1; + value = '\\'; + --str; + break; + + case '0': case '1': case '2': case '3': + case '4': case '5': case '6': case '7': + num_buf[0] = ch; + ch = str[0]; + num_buf[1] = ch; + num_buf[2] = (char)(ch != '\0' ? str[1] : '\0'); + num_buf[3] = '\0'; + value = (char)strtoul(num_buf, &num_end, 8); + str += num_end - (num_buf + 1); + break; + + case 'x': + /* + * Hexadecimal character constants are not required to be + * supported (by SuS v1) because there is no consistent + * way to detect the end of the constant. + * Supporting 2 byte constants is a compromise. + */ + ch = str[0]; + num_buf[0] = ch; + num_buf[1] = (char)(ch != '\0' ? str[1] : '\0'); + num_buf[2] = '\0'; + value = (char)strtoul(num_buf, &num_end, 16); + str += num_end - num_buf; + break; + + case '\\': value = '\\'; break; /* backslash */ + case '\'': value = '\''; break; /* single quote */ + case '"': value = '"'; break; /* double quote */ + case 'a': value = '\a'; break; /* alert */ + case 'b': value = '\b'; break; /* backspace */ + case 'e': value = ESCAPE; break; /* escape */ + case 'E': value = ESCAPE; break; /* escape */ + case 'f': value = '\f'; break; /* form-feed */ + case 'n': value = '\n'; break; /* newline */ + case 'r': value = '\r'; break; /* carriage-return */ + case 't': value = '\t'; break; /* tab */ + case 'v': value = '\v'; break; /* vertical-tab */ + + default: + if (!quiet) + warnx("unknown escape sequence `\\%c'", ch); + rval = 1; + value = ch; + break; + } + + *conv_ch = value; + return str; +} + +/* expand a string so that everything is printable */ + +static char * +conv_expand(const char *str) +{ + static char *conv_str; + char *cp; + char ch; + + if (conv_str) + free(conv_str); + /* get a buffer that is definitely large enough.... */ + conv_str = malloc(4 * strlen(str) + 1); + if (!conv_str) + return NULL; + cp = conv_str; + + while ((ch = *(const char *)str++) != '\0') { + switch (ch) { + /* Use C escapes for expected control characters */ + case '\\': ch = '\\'; break; /* backslash */ + case '\'': ch = '\''; break; /* single quote */ + case '"': ch = '"'; break; /* double quote */ + case '\a': ch = 'a'; break; /* alert */ + case '\b': ch = 'b'; break; /* backspace */ + case ESCAPE: ch = 'e'; break; /* escape */ + case '\f': ch = 'f'; break; /* form-feed */ + case '\n': ch = 'n'; break; /* newline */ + case '\r': ch = 'r'; break; /* carriage-return */ + case '\t': ch = 't'; break; /* tab */ + case '\v': ch = 'v'; break; /* vertical-tab */ + default: + /* Copy anything printable */ + if (isprint((unsigned char)ch)) { + *cp++ = ch; + continue; + } + /* Use vis(3) encodings for the rest */ + *cp++ = '\\'; + if (ch & 0200) { + *cp++ = 'M'; + ch &= (char)~0200; + } + if (ch == 0177) { + *cp++ = '^'; + *cp++ = '?'; + continue; + } + if (ch < 040) { + *cp++ = '^'; + *cp++ = ch | 0100; + continue; + } + *cp++ = '-'; + *cp++ = ch; + continue; + } + *cp++ = '\\'; + *cp++ = ch; + } + + *cp = 0; + return conv_str; +} + +static char * +mklong(const char *str, char ch) +{ + static char copy[64]; + size_t len; + + len = strlen(str) + 2; + if (len > sizeof copy) { + warnx("format %s too complex", str); + len = 4; + } + (void)memmove(copy, str, len - 3); + copy[len - 3] = 'j'; + copy[len - 2] = ch; + copy[len - 1] = '\0'; + return copy; +} + +static char +getchr(void) +{ + if (!*gargv) + return 0; + return **gargv++; +} + +static char * +getstr(void) +{ + static char empty[] = ""; + if (!*gargv) + return empty; + return *gargv++; +} + +static int +getwidth(void) +{ + unsigned long val; + char *s, *ep; + + s = *gargv; + if (s == NULL) + return 0; + gargv++; + + errno = 0; + val = strtoul(s, &ep, 0); + check_conversion(s, ep); + + /* Arbitrarily 'restrict' field widths to 1Mbyte */ + if (val > 1 << 20) { + warnx("%s: invalid field width", s); + return 0; + } + + return (int)val; +} + +static intmax_t +getintmax(void) +{ + intmax_t val; + char *cp, *ep; + + cp = *gargv; + if (cp == NULL) + return 0; + gargv++; + + if (*cp == '\"' || *cp == '\'') + return *(cp + 1); + + errno = 0; + val = strtoimax(cp, &ep, 0); + check_conversion(cp, ep); + return val; +} + +static double +getdouble(void) +{ + double val; + char *ep; + + if (!*gargv) + return 0.0; + + if (**gargv == '\"' || **gargv == '\'') + return (double) *((*gargv++)+1); + + errno = 0; + val = strtod(*gargv, &ep); + check_conversion(*gargv++, ep); + return val; +} + +static void +check_conversion(const char *s, const char *ep) +{ + if (*ep) { + if (ep == s) + warnx("%s: expected numeric value", s); + else + warnx("%s: not completely converted", s); + rval = 1; + } else if (errno == ERANGE) { + warnx("%s: %s", s, strerror(ERANGE)); + rval = 1; + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s format [arg ...]\n", getprogname()); +} diff --git a/usr.bin/renice/renice.8 b/usr.bin/renice/renice.8 new file mode 100644 index 0000000..5dedc3e --- /dev/null +++ b/usr.bin/renice/renice.8 @@ -0,0 +1,151 @@ +.\" $NetBSD: renice.8,v 1.15 2012/12/06 07:52:12 wiz Exp $ +.\" +.\" Copyright (c) 1983, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)renice.8 8.1 (Berkeley) 6/9/93 +.\" +.Dd December 6, 2012 +.Dt RENICE 8 +.Os +.Sh NAME +.Nm renice +.Nd alter priority of running processes +.Sh SYNOPSIS +.Nm +.Ar priority +.Oo +.Op Fl p +.Ar pid ... +.Oc +.Oo +.Fl g +.Ar pgrp ... +.Oc +.Oo +.Fl u +.Ar user ... +.Oc +.Nm +.Fl n +.Ar increment +.Oo +.Op Fl p +.Ar pid ... +.Oc +.Oo +.Fl g +.Ar pgrp ... +.Oc +.Oo +.Fl u +.Ar user ... +.Oc +.Sh DESCRIPTION +.Nm +alters the +scheduling priority of one or more running processes. +The following +.Ar who +parameters are interpreted as process ID's, process group +ID's, or user names. +.Nm Ns 'ing +a process group causes all processes in the process group +to have their scheduling priority altered. +.Nm Ns 'ing +a user causes all processes owned by the user to have +their scheduling priority altered. +By default, the processes to be affected are specified by +their process ID's. +.Pp +Options supported by +.Nm : +.Bl -tag -width Ds +.It Fl g +Force +.Ar who +parameters to be interpreted as process group ID's. +.It Fl n +Instead of changing the specified processes to the given priority, +interpret the following argument as an increment to be applied to +the current priority of each process. +.It Fl u +Force the +.Ar who +parameters to be interpreted as user names. +.It Fl p +Resets the +.Ar who +interpretation to be (the default) process ID's. +.El +.Pp +For example, +.Bd -literal -offset indent +renice +1 987 -u daemon root -p 32 +.Ed +.Pp +would change the priority of process ID's 987 and 32, and +all processes owned by users daemon and root. +.Pp +Users other than the super-user may only alter the priority of +processes they own, +and can only monotonically increase their ``nice value'' +within the range 0 to +.Dv PRIO_MAX +(20). +(This prevents overriding administrative fiats.) +The super-user +may alter the priority of any process +and set the priority to any value in the range +.Dv PRIO_MIN +(\-20) +to +.Dv PRIO_MAX . +.Pp +Useful priorities are: +0, the ``base'' scheduling priority; +20, the affected processes will run only when nothing at the base priority +wants to; +anything negative, the processes will receive a scheduling preference. +.Sh FILES +.Bl -tag -width /etc/passwd -compact +.It Pa /etc/passwd +to map user names to user ID's +.El +.Sh SEE ALSO +.Xr nice 1 , +.Xr prenice 1 , +.Xr getpriority 2 , +.Xr setpriority 2 +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.0 . +.Sh BUGS +Non super-users can not increase scheduling priorities of their own processes, +even if they were the ones that decreased the priorities in the first place. diff --git a/usr.bin/renice/renice.c b/usr.bin/renice/renice.c new file mode 100644 index 0000000..977f081 --- /dev/null +++ b/usr.bin/renice/renice.c @@ -0,0 +1,181 @@ +/* $NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $ */ + +/* + * Copyright (c) 1983, 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +/*static char sccsid[] = "from: @(#)renice.c 8.1 (Berkeley) 6/9/93";*/ +__RCSID("$NetBSD: renice.c,v 1.18 2008/07/21 14:19:25 lukem Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int getnum(const char *, const char *, int *); +static int donice(int, id_t, int, int); +static void usage(void) __dead; + +/* + * Change the priority (nice) of processes + * or groups of processes which are already + * running. + */ +int +main(int argc, char **argv) +{ + int which = PRIO_PROCESS; + int prio, errs = 0, incr = 0; + id_t who = 0; + + argc--, argv++; + if (argc < 2) + usage(); + if (strcmp(*argv, "-n") == 0) { + incr = 1; + argc--, argv++; + if (argc == 0) + usage(); + } + if (getnum("priority", *argv, &prio)) + return 1; + argc--, argv++; + for (; argc > 0; argc--, argv++) { + if (strcmp(*argv, "-g") == 0) { + which = PRIO_PGRP; + continue; + } + if (strcmp(*argv, "-u") == 0) { + which = PRIO_USER; + continue; + } + if (strcmp(*argv, "-p") == 0) { + which = PRIO_PROCESS; + continue; + } + if (which == PRIO_USER) { + struct passwd *pwd = getpwnam(*argv); + + if (pwd == NULL) { + warnx("%s: unknown user", *argv); + errs++; + continue; + } + who = (id_t)pwd->pw_uid; + } else { + int twho; + if (getnum("pid", *argv, &twho)) { + errs++; + continue; + } + if (twho < 0) { + warnx("%s: bad value", *argv); + errs++; + continue; + } + who = (id_t)twho; + } + errs += donice(which, who, prio, incr); + } + return errs == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int +getnum(const char *com, const char *str, int *val) +{ + long v; + char *ep; + + errno = 0; + v = strtol(str, &ep, 0); + + if (*ep) { + warnx("Bad %s argument: %s", com, str); + return 1; + } + if ((v == LONG_MIN || v == LONG_MAX) && errno == ERANGE) { + warn("Invalid %s argument: %s", com, str); + return 1; + } + + *val = (int)v; + return 0; +} + +static int +donice(int which, id_t who, int prio, int incr) +{ + int oldprio; + + errno = 0; + if ((oldprio = getpriority(which, who)) == -1 && errno != 0) { + warn("%d: getpriority", who); + return 1; + } + + if (incr) + prio = oldprio + prio; + + if (prio > PRIO_MAX) + prio = PRIO_MAX; + if (prio < PRIO_MIN) + prio = PRIO_MIN; + + if (setpriority(which, who, prio) == -1) { + warn("%d: setpriority", who); + return 1; + } + (void)printf("%d: old priority %d, new priority %d\n", + who, oldprio, prio); + return 0; +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "Usage: %s [ | -n ] ", + getprogname()); + (void)fprintf(stderr, "[[-p] ...] [-g ...] "); + (void)fprintf(stderr, "[-u ...]\n"); + exit(1); +} diff --git a/usr.bin/sed/POSIX b/usr.bin/sed/POSIX new file mode 100644 index 0000000..cbacd89 --- /dev/null +++ b/usr.bin/sed/POSIX @@ -0,0 +1,205 @@ +# $NetBSD: POSIX,v 1.5 2014/06/06 00:13:13 christos Exp $ +# @(#)POSIX 8.1 (Berkeley) 6/6/93 +# $FreeBSD: head/usr.bin/sed/POSIX 168417 2007-04-06 08:43:30Z yar $ + +Comments on the IEEE P1003.2 Draft 12 + Part 2: Shell and Utilities + Section 4.55: sed - Stream editor + +Diomidis Spinellis +Keith Bostic + +In the following paragraphs, "wrong" usually means "inconsistent with +historic practice", as most of the following comments refer to +undocumented inconsistencies between the historical versions of sed and +the POSIX 1003.2 standard. All the comments are notes taken while +implementing a POSIX-compatible version of sed, and should not be +interpreted as official opinions or criticism towards the POSIX committee. +All uses of "POSIX" refer to section 4.55, Draft 12 of POSIX 1003.2. + + 1. 32V and BSD derived implementations of sed strip the text + arguments of the a, c and i commands of their initial blanks, + i.e. + + #!/bin/sed -f + a\ + foo\ + \ indent\ + bar + + produces: + + foo + indent + bar + + POSIX does not specify this behavior as the System V versions of + sed do not do this stripping. The argument against stripping is + that it is difficult to write sed scripts that have leading blanks + if they are stripped. The argument for stripping is that it is + difficult to write readable sed scripts unless indentation is allowed + and ignored, and leading whitespace is obtainable by entering a + backslash in front of it. This implementation follows the BSD + historic practice. + + 2. Historical versions of sed required that the w flag be the last + flag to an s command as it takes an additional argument. This + is obvious, but not specified in POSIX. + + 3. Historical versions of sed required that whitespace follow a w + flag to an s command. This is not specified in POSIX. This + implementation permits whitespace but does not require it. + + 4. Historical versions of sed permitted any number of whitespace + characters to follow the w command. This is not specified in + POSIX. This implementation permits whitespace but does not + require it. + + 5. The rule for the l command differs from historic practice. Table + 2-15 includes the various ANSI C escape sequences, including \\ + for backslash. Some historical versions of sed displayed two + digit octal numbers, too, not three as specified by POSIX. POSIX + is a cleanup, and is followed by this implementation. + + 6. The POSIX specification for ! does not specify that for a single + command the command must not contain an address specification + whereas the command list can contain address specifications. The + specification for ! implies that "3!/hello/p" works, and it never + has, historically. Note, + + 3!{ + /hello/p + } + + does work. + + 7. POSIX does not specify what happens with consecutive ! commands + (e.g. /foo/!!!p). Historic implementations allow any number of + !'s without changing the behaviour. (It seems logical that each + one might reverse the behaviour.) This implementation follows + historic practice. + + 8. Historic versions of sed permitted commands to be separated + by semi-colons, e.g. 'sed -ne '1p;2p;3q' printed the first + three lines of a file. This is not specified by POSIX. + Note, the ; command separator is not allowed for the commands + a, c, i, w, r, :, b, t, # and at the end of a w flag in the s + command. This implementation follows historic practice and + implements the ; separator. + + 9. Historic versions of sed terminated the script if EOF was reached + during the execution of the 'n' command, i.e.: + + sed -e ' + n + i\ + hello + ' +# +# note -- TWO carriage returns, a peculiarity of sed), this will output the +# sequence of states involved in moving 4 rings, the largest called "a" and +# the smallest called "d", from the first to the second of three towers, so +# that the rings on any tower at any time are in descending order of size. +# You can start with a different arrangement and a different number of rings, +# say :ce:b:ax: and it will give the shortest procedure for moving them all +# to the middle tower. The rules are: the names of the rings must all be +# lower-case letters, they must be input within 3 fields (representing the +# towers) and delimited by 4 colons, such that the letters within each field +# are in alphabetical order (i.e. rings are in descending order of size). +# +# For the benefit of anyone who wants to figure out the script, an "internal" +# line of the form +# b:0abx:1a2b3 :2 :3x2 +# has the following meaning: the material after the three markers :1, :2, +# and :3 represents the three towers; in this case the current set-up is +# ":ab : :x :". The numbers after a, b and x in these fields indicate +# that the next time it gets a chance, it will move a to tower 2, move b +# to tower 3, and move x to tower 2. The string after :0 just keeps track +# of the alphabetical order of the names of the rings. The b at the +# beginning means that it is now dealing with ring b (either about to move +# it, or re-evaluating where it should next be moved to). +# +# Although this version is "limited" to 26 rings because of the size of the +# alphabet, one could write a script using the same idea in which the rings +# were represented by arbitrary [strings][within][brackets], and in place of +# the built-in line of the script giving the order of the letters of the +# alphabet, it would accept from the user a line giving the ordering to be +# assumed, e.g. [ucbvax][decvax][hplabs][foo][bar]. +# +# George Bergman +# Math, UC Berkeley 94720 USA + +# cleaning, diagnostics +s/ *//g +/^$/d +/[^a-z:]/{a\ +Illegal characters: use only a-z and ":". Try again. +d +} +/^:[a-z]*:[a-z]*:[a-z]*:$/!{a\ +Incorrect format: use\ +\ : string1 : string2 : string3 :\ +Try again. +d +} +/\([a-z]\).*\1/{a\ +Repeated letters not allowed. Try again. +d +} +# initial formatting +h +s/[a-z]/ /g +G +s/^:\( *\):\( *\):\( *\):\n:\([a-z]*\):\([a-z]*\):\([a-z]*\):$/:1\4\2\3:2\5\1\3:3\6\1\2:0/ +s/[a-z]/&2/g +s/^/abcdefghijklmnopqrstuvwxyz/ +:a +s/^\(.\).*\1.*/&\1/ +s/.// +/^[^:]/ba +s/\([^0]*\)\(:0.*\)/\2\1:/ +s/^[^0]*0\(.\)/\1&/ +:b +# outputting current state without markers +h +s/.*:1/:/ +s/[123]//gp +g +:c +# establishing destinations +/^\(.\).*\1:1/td +/^\(.\).*:1[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/ +/^\(.\).*:1[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/ +/^\(.\).*:1[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/ +/^\(.\).*:2[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/ +/^\(.\).*:2[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/ +/^\(.\).*:2[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/ +/^\(.\).*:3[^:]*\11/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\32/ +/^\(.\).*:3[^:]*\12/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\31/ +/^\(.\).*:3[^:]*\13/s/^\(.\)\(.*\1\([a-z]\).*\)\3./\3\2\33/ +bc +# iterate back to find smallest out-of-place ring +:d +s/^\(.\)\(:0[^:]*\([^:]\)\1.*:\([123]\)[^:]*\1\)\4/\3\2\4/ +td +# move said ring (right, resp. left) +s/^\(.\)\(.*\)\1\([23]\)\(.*:\3[^ ]*\) /\1\2 \4\1\3/ +s/^\(.\)\(.*:\([12]\)[^ ]*\) \(.*\)\1\3/\1\2\1\3\4 / +tb +s/.*/Done! Try another, or end with ^D./p +d diff --git a/usr.bin/sed/TEST/math.sed b/usr.bin/sed/TEST/math.sed new file mode 100644 index 0000000..82968b5 --- /dev/null +++ b/usr.bin/sed/TEST/math.sed @@ -0,0 +1,164 @@ +# +# from: @(#)math.sed 8.1 (Berkeley) 6/6/93 +# $NetBSD: math.sed,v 1.3 1997/01/09 20:21:36 tls Exp $ +# +# Addition and multiplication in sed. +# ++ for a limited time only do (expr) too!!! +# +# Kevin S Braunsdorf, PUCC UNIX Group, ksb@cc.purdue.edu. +# +# Ex: +# echo "4+7*3" | sed -f %f + +# make sure the expression is well formed +s/[ ]//g +/[+*\/-]$/{ + a\ + poorly formed expression, operator on the end + q +} +/^[+*\/]/{ + a\ + poorly formed expression, leading operator + q +} + +# fill hold space with done token +x +s/^.*/done/ +x + +# main loop, process operators (*, + and () ) +: loop +/^\+/{ + s/// + b loop +} +/^\(.*\)(\([^)]*\))\(.*\)$/{ + H + s//\2/ + x + s/^\(.*\)\n\(.*\)(\([^()]*\))\(.*\)$/()\2@\4@\1/ + x + b loop +} +/^[0-9]*\*/b mul +/^\([0-9]*\)\+\([0-9+*]*\*[0-9]*\)$/{ + s//\2+\1/ + b loop +} +/^[0-9]*\+/{ + s/$/=/ + b add +} +x +/^done$/{ + x + p + d +} +/^()/{ + s/// + x + G + s/\(.*\)\n\([^@]*\)@\([^@]*\)@\(.*\)/\2\1\3/ + x + s/[^@]*@[^@]*@\(.*\)/\1/ + x + b loop +} +i\ +help, stack problem +p +x +p +q + +# turn mul into add until 1*x -> x +: mul +/^0*1\*/{ + s/// + b loop +} +/^\([0-9]*\)0\*/{ + s/^\([0-9]*\)0\*\([0-9]*\)/\1*\20/ + b mul +} +s/^\([0-9]*\)1\*/\10*/ +s/^\([0-9]*\)2\*/\11*/ +s/^\([0-9]*\)3\*/\12*/ +s/^\([0-9]*\)4\*/\13*/ +s/^\([0-9]*\)5\*/\14*/ +s/^\([0-9]*\)6\*/\15*/ +s/^\([0-9]*\)7\*/\16*/ +s/^\([0-9]*\)8\*/\17*/ +s/^\([0-9]*\)9\*/\18*/ +s/\*\([0-9*]*\)/*\1+\1/ +b mul + +# get rid of a plus term until 0+x -> x +: add +/^\+\([0-9+*]*\)=/{ + s//\1/ + b loop +} +/^\([0-9*]*\)\+=/{ + s//\1/ + b loop +} +/^\([0-9]*\)\+\([0-9*+]*\)\+=/{ + s//\2+\1/ + b loop +} +/^\([0-9]*\)0\+\([0-9]*\)\([0-9]\)=/{ + s//\1+\2=\3/ + b add +} +/^\([0-9]*\)\([0-9]\)\+\([0-9]*\)0=/{ + s//\1+\3=\2/ + b add +} +/^\([0-9]*\)0\+\([0-9*+]*\)\+\([0-9]*\)\([0-9]\)=/{ + s//\1+\2+\3=\4/ + b add +} +/^\([0-9]*\)\([0-9]\)\+\([0-9*+]*\)\+\([0-9]*\)0=/{ + s//\1+\3+\4=\2/ + b add +} +s/^\([0-9]*\)1\+/\10+/ +s/^\([0-9]*\)2\+/\11+/ +s/^\([0-9]*\)3\+/\12+/ +s/^\([0-9]*\)4\+/\13+/ +s/^\([0-9]*\)5\+/\14+/ +s/^\([0-9]*\)6\+/\15+/ +s/^\([0-9]*\)7\+/\16+/ +s/^\([0-9]*\)8\+/\17+/ +s/^\([0-9]*\)9\+/\18+/ + +s/9=\([0-9]*\)$/_=\1/ +s/8=\([0-9]*\)$/9=\1/ +s/7=\([0-9]*\)$/8=\1/ +s/6=\([0-9]*\)$/7=\1/ +s/5=\([0-9]*\)$/6=\1/ +s/4=\([0-9]*\)$/5=\1/ +s/3=\([0-9]*\)$/4=\1/ +s/2=\([0-9]*\)$/3=\1/ +s/1=\([0-9]*\)$/2=\1/ +/_/{ + s//_0/ + : inc + s/9_/_0/ + s/8_/9/ + s/7_/8/ + s/6_/7/ + s/5_/6/ + s/4_/5/ + s/3_/4/ + s/2_/3/ + s/1_/2/ + s/0_/1/ + s/\+_/+1/ + /_/b inc +} +b add diff --git a/usr.bin/sed/TEST/sed.test b/usr.bin/sed/TEST/sed.test new file mode 100644 index 0000000..ff4093a --- /dev/null +++ b/usr.bin/sed/TEST/sed.test @@ -0,0 +1,554 @@ +#!/bin/sh - +# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $ +# +# Copyright (c) 1992 Diomidis Spinellis. +# Copyright (c) 1992, 1993 +# The Regents of the University of California. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# from: @(#)sed.test 8.1 (Berkeley) 6/6/93 +# $NetBSD: sed.test,v 1.5 2011/11/12 03:15:05 christos Exp $ +# + +# sed Regression Tests +# +# The following files are created: +# lines[1-4], script1, script2 +# Two directories *.out contain the test results + +main() +{ + BASE=/usr/bin/sed + BASELOG=sed.out + TEST=$(cd $(dirname $0)/.. && make -V .OBJDIR)/sed + TESTLOG=nsed.out + DICT=/usr/share/dict/words + + test_error | more + + awk 'END { for (i = 1; i < 15; i++) print "l1_" i}' lines1 + awk 'END { for (i = 1; i < 10; i++) print "l2_" i}' lines2 + + exec 4>&1 5>&2 + + # Set these flags to get messages about known problems + BSD=1 + GNU=0 + SUN=0 + tests $BASE $BASELOG + + BSD=0 + GNU=0 + SUN=0 + tests $TEST $TESTLOG + exec 1>&4 2>&5 + diff -c $BASELOG $TESTLOG | more +} + +tests() +{ + SED=$1 + DIR=$2 + rm -rf $DIR + mkdir $DIR + MARK=100 + + test_args + test_addr + echo Testing commands + test_group + test_acid + test_branch + test_pattern + test_print + test_subst +} + +mark() +{ + MARK=$(expr $MARK + 1) + exec 1>&4 2>&5 + exec >"$DIR/${MARK}_$1" + echo "Test $1:$MARK" + # Uncomment this line to match tests with sed error messages + echo "Test $1:$MARK" >&5 +} + +test_args() +{ + mark '1.1' + echo Testing argument parsing + echo First type + if [ $SUN -eq 1 ] ; then + echo SunOS sed prints only with -n + else + $SED 's/^/e1_/p' lines1 + fi + mark '1.2' ; $SED -n 's/^/e1_/p' lines1 + mark '1.3' + if [ $SUN -eq 1 ] ; then + echo SunOS sed prints only with -n + else + $SED 's/^/e1_/p' script1 + echo 's/^/s2_/p' >script2 + mark '1.5' + if [ $SUN -eq 1 ] ; then + echo SunOS sed prints only with -n + else + $SED -f script1 lines1 + fi + mark '1.6' + if [ $SUN -eq 1 ] ; then + echo SunOS sed prints only with -n + else + $SED -f script1 script1 <lines3 + # GNU and SunOS sed behave differently here + mark '7.1' + if [ $BSD -eq 1 ] ; then + echo 'BSD sed drops core on this one; TEST SKIPPED' + else + $SED -n l lines3 + fi + mark '7.2' ; $SED -e '/l2_/=' lines1 lines2 + rm -f lines4 + mark '7.3' ; $SED -e '3,12w lines4' lines1 + echo w results + cat lines4 + mark '7.4' ; $SED -e '4r lines2' lines1 + mark '7.5' ; $SED -e '5r /dev/dds' lines1 + mark '7.6' ; $SED -e '6r /dev/null' lines1 + mark '7.7' + if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then + echo BSD, GNU and SunOS cannot pass this one + else + sed '200q' $DICT | sed 's$.*$s/^/&/w tmpdir/&$' >script1 + rm -rf tmpdir + mkdir tmpdir + $SED -f script1 lines1 + cat tmpdir/* + rm -rf tmpdir + fi + mark '7.8' + if [ $BSD -eq 1 ] ; then + echo BSD sed cannot pass 7.7 + else + echo line1 > lines3 + echo "" >> lines3 + $SED -n -e '$p' lines3 /dev/null + fi + +} + +test_subst() +{ + echo Testing substitution commands + mark '8.1' ; $SED -e 's/./X/g' lines1 + mark '8.2' ; $SED -e 's,.,X,g' lines1 +# GNU and SunOS sed thinks we are escaping . as wildcard, not as separator +# mark '8.3' ; $SED -e 's.\..X.g' lines1 +# POSIX does not say that this should work +# mark '8.4' ; $SED -e 's/[/]/Q/' lines1 + mark '8.4' ; $SED -e 's/[\/]/Q/' lines1 + mark '8.5' ; $SED -e 's_\__X_' lines1 + mark '8.6' ; $SED -e 's/./(&)/g' lines1 + mark '8.7' ; $SED -e 's/./(\&)/g' lines1 + mark '8.8' ; $SED -e 's/\(.\)\(.\)\(.\)/x\3x\2x\1/g' lines1 + mark '8.9' ; $SED -e 's/_/u0\ +u1\ +u2/g' lines1 + mark '8.10' + if [ $BSD -eq 1 -o $GNU -eq 1 ] ; then + echo 'BSD/GNU sed do not understand digit flags on s commands' + fi + $SED -e 's/./X/4' lines1 + rm -f lines4 + mark '8.11' ; $SED -e 's/1/X/w lines4' lines1 + echo s wfile results + cat lines4 + mark '8.12' ; $SED -e 's/[123]/X/g' lines1 + mark '8.13' ; $SED -e 'y/0123456789/9876543210/' lines1 + mark '8.14' ; + if [ $BSD -eq 1 -o $GNU -eq 1 -o $SUN -eq 1 ] ; then + echo BSD/GNU/SUN sed fail this test + else + $SED -e 'y10\123456789198765432\101' lines1 + fi + mark '8.15' ; $SED -e '1N;2y/\n/X/' lines1 + mark '8.16' + if [ $BSD -eq 1 ] ; then + echo 'BSD sed does not handle branch defined REs' + else + echo 'eeefff' | $SED -e 'p' -e 's/e/X/p' -e ':x' \ + -e 's//Y/p' -e '/f/bx' + fi +} + +test_error() +{ + exec 3<&0 4>&1 5>&2 + exec 0&1 + set -x + $TEST -x && exit 1 + $TEST -f && exit 1 + $TEST -e && exit 1 + $TEST -f /dev/dds && exit 1 + $TEST p /dev/dds && exit 1 + $TEST -f /bin/sh && exit 1 + $TEST '{' && exit 1 + $TEST '{' && exit 1 + $TEST '/hello/' && exit 1 + $TEST '1,/hello/' && exit 1 + $TEST -e '-5p' && exit 1 + $TEST '/jj' && exit 1 + $TEST 'a hello' && exit 1 + $TEST 'a \ hello' && exit 1 + $TEST 'b foo' && exit 1 + $TEST 'd hello' && exit 1 + $TEST 's/aa' && exit 1 + $TEST 's/aa/' && exit 1 + $TEST 's/a/b' && exit 1 + $TEST 's/a/b/c/d' && exit 1 + $TEST 's/a/b/ 1 2' && exit 1 + $TEST 's/a/b/ 1 g' && exit 1 + $TEST 's/a/b/w' && exit 1 + $TEST 'y/aa' && exit 1 + $TEST 'y/aa/b/' && exit 1 + $TEST 'y/aa/' && exit 1 + $TEST 'y/a/b' && exit 1 + $TEST 'y/a/b/c/d' && exit 1 + $TEST '!' && exit 1 + $TEST supercalifrangolisticexprialidociussupercalifrangolisticexcius + set +x + exec 0<&3 1>&4 2>&5 +} + +main diff --git a/usr.bin/sed/compile.c b/usr.bin/sed/compile.c new file mode 100644 index 0000000..300a77f --- /dev/null +++ b/usr.bin/sed/compile.c @@ -0,0 +1,946 @@ +/* $NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $ */ + +/*- + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: compile.c,v 1.47 2016/04/05 00:13:03 christos Exp $"); +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/usr.bin/sed/compile.c 259132 2013-12-09 18:57:20Z eadler $"); +#endif + +#if 0 +static const char sccsid[] = "@(#)compile.c 8.1 (Berkeley) 6/6/93"; +#endif + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "extern.h" + +#define LHSZ 128 +#define LHMASK (LHSZ - 1) +static struct labhash { + struct labhash *lh_next; + u_int lh_hash; + struct s_command *lh_cmd; + int lh_ref; +} *labels[LHSZ]; + +static char *compile_addr(char *, struct s_addr *); +static char *compile_ccl(char **, char *); +static char *compile_delimited(char *, char *, int); +static char *compile_flags(char *, struct s_subst *); +static regex_t *compile_re(char *, int); +static char *compile_subst(char *, struct s_subst *); +static char *compile_text(void); +static char *compile_tr(char *, struct s_tr **); +static struct s_command + **compile_stream(struct s_command **); +static char *duptoeol(char *, const char *); +static void enterlabel(struct s_command *); +static struct s_command + *findlabel(char *); +static void fixuplabel(struct s_command *, struct s_command *); +static void uselabel(void); + +/* + * Command specification. This is used to drive the command parser. + */ +struct s_format { + char code; /* Command code */ + int naddr; /* Number of address args */ + enum e_args args; /* Argument type */ +}; + +static struct s_format cmd_fmts[] = { + {'{', 2, GROUP}, + {'}', 0, ENDGROUP}, + {'a', 1, TEXT}, + {'b', 2, BRANCH}, + {'c', 2, TEXT}, + {'d', 2, EMPTY}, + {'D', 2, EMPTY}, + {'g', 2, EMPTY}, + {'G', 2, EMPTY}, + {'h', 2, EMPTY}, + {'H', 2, EMPTY}, + {'i', 1, TEXT}, + {'l', 2, EMPTY}, + {'n', 2, EMPTY}, + {'N', 2, EMPTY}, + {'p', 2, EMPTY}, + {'P', 2, EMPTY}, + {'q', 1, EMPTY}, + {'r', 1, RFILE}, + {'s', 2, SUBST}, + {'t', 2, BRANCH}, + {'w', 2, WFILE}, + {'x', 2, EMPTY}, + {'y', 2, TR}, + {'!', 2, NONSEL}, + {':', 0, LABEL}, + {'#', 0, COMMENT}, + {'=', 1, EMPTY}, + {'\0', 0, COMMENT}, +}; + +/* The compiled program. */ +struct s_command *prog; + +/* + * Compile the program into prog. + * Initialise appends. + */ +void +compile(void) +{ + *compile_stream(&prog) = NULL; + fixuplabel(prog, NULL); + uselabel(); + if (appendnum > 0) + appends = xmalloc(sizeof(struct s_appends) * appendnum); + match = xmalloc((maxnsub + 1) * sizeof(regmatch_t)); +} + +#define EATSPACE() do { \ + if (p) \ + while (*p && isspace((unsigned char)*p)) \ + p++; \ + } while (0) + +static struct s_command ** +compile_stream(struct s_command **link) +{ + char *p; + static char lbuf[_POSIX2_LINE_MAX + 1]; /* To save stack */ + struct s_command *cmd, *cmd2, *stack; + struct s_format *fp; + char re[_POSIX2_LINE_MAX + 1]; + int naddr; /* Number of addresses */ + + stack = 0; + for (;;) { + if ((p = cu_fgets(lbuf, sizeof(lbuf), NULL)) == NULL) { + if (stack != 0) + errx(1, "%lu: %s: unexpected EOF (pending }'s)", + linenum, fname); + return (link); + } + +semicolon: EATSPACE(); + if (p) { + if (*p == '#' || *p == '\0') + continue; + else if (*p == ';') { + p++; + goto semicolon; + } + } + *link = cmd = xmalloc(sizeof(struct s_command)); + link = &cmd->next; + cmd->startline = cmd->nonsel = 0; + /* First parse the addresses */ + naddr = 0; + +/* Valid characters to start an address */ +#define addrchar(c) (strchr("0123456789/\\$", (c))) + if (addrchar(*p)) { + naddr++; + cmd->a1 = xmalloc(sizeof(struct s_addr)); + p = compile_addr(p, cmd->a1); + EATSPACE(); /* EXTENSION */ + if (*p == ',') { + p++; + EATSPACE(); /* EXTENSION */ + naddr++; + cmd->a2 = xmalloc(sizeof(struct s_addr)); + p = compile_addr(p, cmd->a2); + EATSPACE(); + } else + cmd->a2 = 0; + } else + cmd->a1 = cmd->a2 = 0; + +nonsel: /* Now parse the command */ + if (!*p) + errx(1, "%lu: %s: command expected", linenum, fname); + cmd->code = *p; + for (fp = cmd_fmts; fp->code; fp++) + if (fp->code == *p) + break; + if (!fp->code) + errx(1, "%lu: %s: invalid command code %c", linenum, fname, *p); + if (naddr > fp->naddr) + errx(1, + "%lu: %s: command %c expects up to %d address(es), found %d", + linenum, fname, *p, fp->naddr, naddr); + switch (fp->args) { + case NONSEL: /* ! */ + p++; + EATSPACE(); + cmd->nonsel = ! cmd->nonsel; + goto nonsel; + case GROUP: /* { */ + p++; + EATSPACE(); + cmd->next = stack; + stack = cmd; + link = &cmd->u.c; + if (*p) + goto semicolon; + break; + case ENDGROUP: + /* + * Short-circuit command processing, since end of + * group is really just a noop. + */ + cmd->nonsel = 1; + if (stack == 0) + errx(1, "%lu: %s: unexpected }", linenum, fname); + cmd2 = stack; + stack = cmd2->next; + cmd2->next = cmd; + /*FALLTHROUGH*/ + case EMPTY: /* d D g G h H l n N p P q x = \0 */ + p++; + EATSPACE(); + switch (*p) { + case ';': + p++; + link = &cmd->next; + goto semicolon; + case '}': + goto semicolon; + case '\0': + break; + default: + errx(1, "%lu: %s: extra characters at the end of %c command", + linenum, fname, cmd->code); + } + break; + case TEXT: /* a c i */ + p++; + EATSPACE(); + if (*p != '\\') + errx(1, +"%lu: %s: command %c expects \\ followed by text", linenum, fname, cmd->code); + p++; + EATSPACE(); + if (*p) + errx(1, + "%lu: %s: extra characters after \\ at the end of %c command", + linenum, fname, cmd->code); + cmd->t = compile_text(); + break; + case COMMENT: /* \0 # */ + break; + case WFILE: /* w */ + p++; + EATSPACE(); + if (*p == '\0') + errx(1, "%lu: %s: filename expected", linenum, fname); + cmd->t = duptoeol(p, "w command"); + if (aflag) + cmd->u.fd = -1; + else if ((cmd->u.fd = open(p, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, + DEFFILEMODE)) == -1) + err(1, "%s", p); + break; + case RFILE: /* r */ + p++; + EATSPACE(); + if (*p == '\0') + errx(1, "%lu: %s: filename expected", linenum, fname); + else + cmd->t = duptoeol(p, "read command"); + break; + case BRANCH: /* b t */ + p++; + EATSPACE(); + if (*p == '\0') + cmd->t = NULL; + else + cmd->t = duptoeol(p, "branch"); + break; + case LABEL: /* : */ + p++; + EATSPACE(); + cmd->t = duptoeol(p, "label"); + if (strlen(p) == 0) + errx(1, "%lu: %s: empty label", linenum, fname); + enterlabel(cmd); + break; + case SUBST: /* s */ + p++; + if (*p == '\0' || *p == '\\') + errx(1, +"%lu: %s: substitute pattern can not be delimited by newline or backslash", + linenum, fname); + cmd->u.s = xcalloc(1, sizeof(struct s_subst)); + p = compile_delimited(p, re, 0); + if (p == NULL) + errx(1, + "%lu: %s: unterminated substitute pattern", linenum, fname); + + /* Compile RE with no case sensitivity temporarily */ + if (*re == '\0') + cmd->u.s->re = NULL; + else + cmd->u.s->re = compile_re(re, 0); + --p; + p = compile_subst(p, cmd->u.s); + p = compile_flags(p, cmd->u.s); + + /* Recompile RE with case sensitivity from "I" flag if any */ + if (*re == '\0') + cmd->u.s->re = NULL; + else + cmd->u.s->re = compile_re(re, cmd->u.s->icase); + EATSPACE(); + if (*p == ';') { + p++; + link = &cmd->next; + goto semicolon; + } + break; + case TR: /* y */ + p++; + p = compile_tr(p, &cmd->u.y); + EATSPACE(); + switch (*p) { + case ';': + p++; + link = &cmd->next; + goto semicolon; + case '}': + goto semicolon; + case '\0': + break; + default: + errx(1, +"%lu: %s: extra text at the end of a transform command", linenum, fname); + } + if (*p) + break; + } + } +} + +/* + * Get a delimited string. P points to the delimeter of the string; d points + * to a buffer area. Newline and delimiter escapes are processed; other + * escapes are ignored. + * + * Returns a pointer to the first character after the final delimiter or NULL + * in the case of a non-terminated string. The character array d is filled + * with the processed string. + */ +static char * +compile_delimited(char *p, char *d, int is_tr) +{ + char c; + + c = *p++; + if (c == '\0') + return (NULL); + else if (c == '\\') + errx(1, "%lu: %s: \\ can not be used as a string delimiter", + linenum, fname); + else if (c == '\n') + errx(1, "%lu: %s: newline can not be used as a string delimiter", + linenum, fname); + while (*p) { + if (*p == '[' && *p != c) { + if ((d = compile_ccl(&p, d)) == NULL) + errx(1, "%lu: %s: unbalanced brackets ([])", linenum, fname); + continue; + } else if (*p == '\\' && p[1] == '[') { + *d++ = *p++; + } else if (*p == '\\' && p[1] == c) + p++; + else if (*p == '\\' && p[1] == 'n') { + *d++ = '\n'; + p += 2; + continue; + } else if (*p == '\\' && p[1] == '\\') { + if (is_tr) + p++; + else + *d++ = *p++; + } else if (*p == c) { + *d = '\0'; + return (p + 1); + } + *d++ = *p++; + } + return (NULL); +} + + +/* compile_ccl: expand a POSIX character class */ +static char * +compile_ccl(char **sp, char *t) +{ + int c, d; + char *s = *sp; + + *t++ = *s++; + if (*s == '^') + *t++ = *s++; + if (*s == ']') + *t++ = *s++; + for (; *s && (*t = *s) != ']'; s++, t++) + if (*s == '[' && ((d = *(s+1)) == '.' || d == ':' || d == '=')) { + *++t = *++s, t++, s++; + for (c = *s; (*t = *s) != ']' || c != d; s++, t++) + if ((c = *s) == '\0') + return NULL; + } + return (*s == ']') ? *sp = ++s, ++t : NULL; +} + +/* + * Compiles the regular expression in RE and returns a pointer to the compiled + * regular expression. + * Cflags are passed to regcomp. + */ +static regex_t * +compile_re(char *re, int case_insensitive) +{ + regex_t *rep; + int eval, flags; + + + flags = rflags; + if (case_insensitive) + flags |= REG_ICASE; + rep = xmalloc(sizeof(regex_t)); + if ((eval = regcomp(rep, re, flags)) != 0) + errx(1, "%lu: %s: RE error: %s", + linenum, fname, strregerror(eval, rep)); + if (maxnsub < rep->re_nsub) + maxnsub = rep->re_nsub; + return (rep); +} + +/* + * Compile the substitution string of a regular expression and set res to + * point to a saved copy of it. Nsub is the number of parenthesized regular + * expressions. + */ +static char * +compile_subst(char *p, struct s_subst *s) +{ + static char lbuf[_POSIX2_LINE_MAX + 1]; + size_t asize, size; + u_char ref; + char c, *text, *op, *sp; + int more = 1, sawesc = 0; + + c = *p++; /* Terminator character */ + if (c == '\0') + return (NULL); + + s->maxbref = 0; + s->linenum = linenum; + asize = 2 * _POSIX2_LINE_MAX + 1; + text = xmalloc(asize); + size = 0; + do { + op = sp = text + size; + for (; *p; p++) { + if (*p == '\\' || sawesc) { + /* + * If this is a continuation from the last + * buffer, we won't have a character to + * skip over. + */ + if (sawesc) + sawesc = 0; + else + p++; + + if (*p == '\0') { + /* + * This escaped character is continued + * in the next part of the line. Note + * this fact, then cause the loop to + * exit w/ normal EOL case and reenter + * above with the new buffer. + */ + sawesc = 1; + p--; + continue; + } else if (strchr("123456789", *p) != NULL) { + *sp++ = '\\'; + ref = (u_char)(*p - '0'); + if (s->re != NULL && + ref > s->re->re_nsub) + errx(1, "%lu: %s: \\%c not defined in the RE", + linenum, fname, *p); + if (s->maxbref < ref) + s->maxbref = ref; + } else if (*p == '&' || *p == '\\') + *sp++ = '\\'; + } else if (*p == c) { + if (*++p == '\0' && more) { + if (cu_fgets(lbuf, sizeof(lbuf), &more)) + p = lbuf; + } + *sp++ = '\0'; + size += (size_t)(sp - op); + s->new = xrealloc(text, size); + return (p); + } else if (*p == '\n') { + errx(1, +"%lu: %s: unescaped newline inside substitute pattern", linenum, fname); + /* NOTREACHED */ + } + *sp++ = *p; + } + size += (size_t)(sp - op); + if (asize - size < _POSIX2_LINE_MAX + 1) { + asize *= 2; + text = xrealloc(text, asize); + } + } while (cu_fgets(p = lbuf, sizeof(lbuf), &more)); + errx(1, "%lu: %s: unterminated substitute in regular expression", + linenum, fname); + /* NOTREACHED */ +} + +/* + * Compile the flags of the s command + */ +static char * +compile_flags(char *p, struct s_subst *s) +{ + int gn; /* True if we have seen g or n */ + unsigned long nval; + char wfile[_POSIX2_LINE_MAX + 1], *q; + + s->n = 1; /* Default */ + s->p = 0; + s->wfile = NULL; + s->wfd = -1; + s->icase = 0; + for (gn = 0;;) { + EATSPACE(); /* EXTENSION */ + switch (*p) { + case 'g': + if (gn) + errx(1, +"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname); + gn = 1; + s->n = 0; + break; + case '\0': + case '\n': + case ';': + return (p); + case 'p': + s->p = 1; + break; + case 'i': + case 'I': + s->icase = 1; + break; + case '1': case '2': case '3': + case '4': case '5': case '6': + case '7': case '8': case '9': + if (gn) + errx(1, +"%lu: %s: more than one number or 'g' in substitute flags", linenum, fname); + gn = 1; + errno = 0; + nval = strtoul(p, &p, 10); + if (errno == ERANGE || nval > INT_MAX) + errx(1, +"%lu: %s: overflow in the 'N' substitute flag", linenum, fname); + s->n = (int)nval; + p--; + break; + case 'w': + p++; +#ifdef HISTORIC_PRACTICE + if (*p != ' ') { + warnx("%lu: %s: space missing before w wfile", linenum, fname); + return (p); + } +#endif + EATSPACE(); + q = wfile; + while (*p) { + if (*p == '\n') + break; + *q++ = *p++; + } + *q = '\0'; + if (q == wfile) + errx(1, "%lu: %s: no wfile specified", linenum, fname); + s->wfile = strdup(wfile); + if (!aflag && (s->wfd = open(wfile, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, + DEFFILEMODE)) == -1) + err(1, "%s", wfile); + return (p); + default: + errx(1, "%lu: %s: bad flag in substitute command: '%c'", + linenum, fname, *p); + break; + } + p++; + } +} + +/* + * Compile a translation set of strings into a lookup table. + */ +static char * +compile_tr(char *p, struct s_tr **py) +{ + struct s_tr *y; + size_t i; + const char *op, *np; + char old[_POSIX2_LINE_MAX + 1]; + char new[_POSIX2_LINE_MAX + 1]; + size_t oclen, oldlen, nclen, newlen; + mbstate_t mbs1, mbs2; + + *py = y = xmalloc(sizeof(*y)); + y->multis = NULL; + y->nmultis = 0; + + if (*p == '\0' || *p == '\\') + errx(1, + "%lu: %s: transform pattern can not be delimited by newline or backslash", + linenum, fname); + p = compile_delimited(p, old, 1); + if (p == NULL) + errx(1, "%lu: %s: unterminated transform source string", + linenum, fname); + p = compile_delimited(p - 1, new, 1); + if (p == NULL) + errx(1, "%lu: %s: unterminated transform target string", + linenum, fname); + EATSPACE(); + op = old; + oldlen = mbsrtowcs(NULL, &op, 0, NULL); + if (oldlen == (size_t)-1) + err(1, NULL); + np = new; + newlen = mbsrtowcs(NULL, &np, 0, NULL); + if (newlen == (size_t)-1) + err(1, NULL); + if (newlen != oldlen) + errx(1, "%lu: %s: transform strings are not the same length", + linenum, fname); + if (MB_CUR_MAX == 1) { + /* + * The single-byte encoding case is easy: generate a + * lookup table. + */ + for (i = 0; i <= UCHAR_MAX; i++) + y->bytetab[i] = (u_char)i; + for (; *op; op++, np++) + y->bytetab[(u_char)*op] = (u_char)*np; + } else { + /* + * Multi-byte encoding case: generate a lookup table as + * above, but only for single-byte characters. The first + * bytes of multi-byte characters have their lookup table + * entries set to 0, which causes do_tr() to search through + * an auxiliary vector of multi-byte mappings. + */ + memset(&mbs1, 0, sizeof(mbs1)); + memset(&mbs2, 0, sizeof(mbs2)); + for (i = 0; i <= UCHAR_MAX; i++) + y->bytetab[i] = (u_char)((btowc((int)i) != WEOF) ? i : 0); + while (*op != '\0') { + oclen = mbrlen(op, MB_LEN_MAX, &mbs1); + if (oclen == (size_t)-1 || oclen == (size_t)-2) + errc(1, EILSEQ, NULL); + nclen = mbrlen(np, MB_LEN_MAX, &mbs2); + if (nclen == (size_t)-1 || nclen == (size_t)-2) + errc(1, EILSEQ, NULL); + if (oclen == 1 && nclen == 1) + y->bytetab[(u_char)*op] = (u_char)*np; + else { + y->bytetab[(u_char)*op] = 0; + y->multis = xrealloc(y->multis, + (y->nmultis + 1) * sizeof(*y->multis)); + i = y->nmultis++; + y->multis[i].fromlen = oclen; + memcpy(y->multis[i].from, op, oclen); + y->multis[i].tolen = nclen; + memcpy(y->multis[i].to, np, nclen); + } + op += oclen; + np += nclen; + } + } + return (p); +} + +/* + * Compile the text following an a or i command. + */ +static char * +compile_text(void) +{ + size_t asize, size; + int esc_nl; + char *text, *p, *op, *s; + char lbuf[_POSIX2_LINE_MAX + 1]; + + asize = 2 * _POSIX2_LINE_MAX + 1; + text = xmalloc(asize); + size = 0; + while (cu_fgets(lbuf, sizeof(lbuf), NULL)) { + op = s = text + size; + p = lbuf; + for (esc_nl = 0; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0' && *++p == '\n') + esc_nl = 1; + *s++ = *p; + } + size += (size_t)(s - op); + if (!esc_nl) { + *s = '\0'; + break; + } + if (asize - size < _POSIX2_LINE_MAX + 1) { + asize *= 2; + text = xrealloc(text, asize); + } + } + text[size] = '\0'; + p = xrealloc(text, size + 1); + return (p); +} + +/* + * Get an address and return a pointer to the first character after + * it. Fill the structure pointed to according to the address. + */ +static char * +compile_addr(char *p, struct s_addr *a) +{ + char *end, re[_POSIX2_LINE_MAX + 1]; + int icase; + + icase = 0; + + a->type = 0; + switch (*p) { + case '\\': /* Context address */ + ++p; + /* FALLTHROUGH */ + case '/': /* Context address */ + p = compile_delimited(p, re, 0); + if (p == NULL) + errx(1, "%lu: %s: unterminated regular expression", linenum, fname); + /* Check for case insensitive regexp flag */ + if (*p == 'I') { + icase = 1; + p++; + } + if (*re == '\0') + a->u.r = NULL; + else + a->u.r = compile_re(re, icase); + a->type = AT_RE; + return (p); + + case '$': /* Last line */ + a->type = AT_LAST; + return (p + 1); + + case '+': /* Relative line number */ + a->type = AT_RELLINE; + p++; + /* FALLTHROUGH */ + /* Line number */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + if (a->type == 0) + a->type = AT_LINE; + a->u.l = strtoul(p, &end, 10); + return (end); + default: + errx(1, "%lu: %s: expected context address", linenum, fname); + return (NULL); + } +} + +/* + * duptoeol -- + * Return a copy of all the characters up to \n or \0. + */ +static char * +duptoeol(char *s, const char *ctype) +{ + size_t len; + int ws; + char *p, *start; + + ws = 0; + for (start = s; *s != '\0' && *s != '\n'; ++s) + ws = isspace((unsigned char)*s); + *s = '\0'; + if (ws) + warnx("%lu: %s: whitespace after %s", linenum, fname, ctype); + len = (size_t)(s - start + 1); + p = xmalloc(len); + return (memmove(p, start, len)); +} + +/* + * Convert goto label names to addresses, and count a and r commands, in + * the given subset of the script. Free the memory used by labels in b + * and t commands (but not by :). + * + * TODO: Remove } nodes + */ +static void +fixuplabel(struct s_command *cp, struct s_command *end) +{ + + for (; cp != end; cp = cp->next) + switch (cp->code) { + case 'a': + case 'r': + appendnum++; + break; + case 'b': + case 't': + /* Resolve branch target. */ + if (cp->t == NULL) { + cp->u.c = NULL; + break; + } + if ((cp->u.c = findlabel(cp->t)) == NULL) + errx(1, "%lu: %s: undefined label '%s'", linenum, fname, cp->t); + free(cp->t); + break; + case '{': + /* Do interior commands. */ + fixuplabel(cp->u.c, cp->next); + break; + } +} + +/* + * Associate the given command label for later lookup. + */ +static void +enterlabel(struct s_command *cp) +{ + struct labhash **lhp, *lh; + u_char *p; + u_int h, c; + + for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++) + h = (h << 5) + h + c; + lhp = &labels[h & LHMASK]; + for (lh = *lhp; lh != NULL; lh = lh->lh_next) + if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0) + errx(1, "%lu: %s: duplicate label '%s'", linenum, fname, cp->t); + lh = xmalloc(sizeof *lh); + lh->lh_next = *lhp; + lh->lh_hash = h; + lh->lh_cmd = cp; + lh->lh_ref = 0; + *lhp = lh; +} + +/* + * Find the label contained in the command l in the command linked + * list cp. L is excluded from the search. Return NULL if not found. + */ +static struct s_command * +findlabel(char *name) +{ + struct labhash *lh; + u_char *p; + u_int h, c; + + for (h = 0, p = (u_char *)name; (c = *p) != 0; p++) + h = (h << 5) + h + c; + for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) { + if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) { + lh->lh_ref = 1; + return (lh->lh_cmd); + } + } + return (NULL); +} + +/* + * Warn about any unused labels. As a side effect, release the label hash + * table space. + */ +static void +uselabel(void) +{ + struct labhash *lh, *next; + int i; + + for (i = 0; i < LHSZ; i++) { + for (lh = labels[i]; lh != NULL; lh = next) { + next = lh->lh_next; + if (!lh->lh_ref) + warnx("%lu: %s: unused label '%s'", + linenum, fname, lh->lh_cmd->t); + free(lh); + } + } +} diff --git a/usr.bin/sed/defs.h b/usr.bin/sed/defs.h new file mode 100644 index 0000000..b564115 --- /dev/null +++ b/usr.bin/sed/defs.h @@ -0,0 +1,150 @@ +/* $NetBSD: defs.h,v 1.12 2014/06/06 21:56:39 wiz Exp $ */ + +/*- + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)defs.h 8.1 (Berkeley) 6/6/93 + * $FreeBSD: head/usr.bin/sed/defs.h 192732 2009-05-25 06:45:33Z brian $ + */ + +/* + * Types of address specifications + */ +enum e_atype { + AT_RE = 1, /* Line that match RE */ + AT_LINE, /* Specific line */ + AT_RELLINE, /* Relative line */ + AT_LAST /* Last line */ +}; + +/* + * Format of an address + */ +struct s_addr { + enum e_atype type; /* Address type */ + union { + u_long l; /* Line number */ + regex_t *r; /* Regular expression */ + } u; +}; + +/* + * Substitution command + */ +struct s_subst { + int n; /* Occurrence to subst. */ + int p; /* True if p flag */ + int icase; /* True if I flag */ + char *wfile; /* NULL if no wfile */ + int wfd; /* Cached file descriptor */ + regex_t *re; /* Regular expression */ + unsigned int maxbref; /* Largest backreference. */ + u_long linenum; /* Line number. */ + char *new; /* Replacement text */ +}; + +/* + * Translate command. + */ +struct s_tr { + unsigned char bytetab[256]; + struct trmulti { + size_t fromlen; + char from[MB_LEN_MAX]; + size_t tolen; + char to[MB_LEN_MAX]; + } *multis; + size_t nmultis; +}; + +/* + * An internally compiled command. + * Initialy, label references are stored in t, on a second pass they + * are updated to pointers. + */ +struct s_command { + struct s_command *next; /* Pointer to next command */ + struct s_addr *a1, *a2; /* Start and end address */ + u_long startline; /* Start line number or zero */ + char *t; /* Text for : a c i r w */ + union { + struct s_command *c; /* Command(s) for b t { */ + struct s_subst *s; /* Substitute command */ + struct s_tr *y; /* Replace command array */ + int fd; /* File descriptor for w */ + } u; + char code; /* Command code */ + u_int nonsel:1; /* True if ! */ +}; + +/* + * Types of command arguments recognised by the parser + */ +enum e_args { + EMPTY, /* d D g G h H l n N p P q x = \0 */ + TEXT, /* a c i */ + NONSEL, /* ! */ + GROUP, /* { */ + ENDGROUP, /* } */ + COMMENT, /* # */ + BRANCH, /* b t */ + LABEL, /* : */ + RFILE, /* r */ + WFILE, /* w */ + SUBST, /* s */ + TR /* y */ +}; + +/* + * Structure containing things to append before a line is read + */ +struct s_appends { + enum {AP_STRING, AP_FILE} type; + char *s; + size_t len; +}; + +enum e_spflag { + APPEND, /* Append to the contents. */ + REPLACE /* Replace the contents. */ +}; + +/* + * Structure for a space (process, hold, otherwise). + */ +typedef struct { + char *space; /* Current space pointer. */ + size_t len; /* Current length. */ + int deleted; /* If deleted. */ + char *back; /* Backing memory. */ + size_t blen; /* Backing memory length. */ +} SPACE; diff --git a/usr.bin/sed/extern.h b/usr.bin/sed/extern.h new file mode 100644 index 0000000..0d28591 --- /dev/null +++ b/usr.bin/sed/extern.h @@ -0,0 +1,61 @@ +/* $NetBSD: extern.h,v 1.20 2015/03/12 12:40:41 christos Exp $ */ + +/*- + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + * $FreeBSD: head/usr.bin/sed/extern.h 170608 2007-06-12 12:05:24Z yar $ + */ + +extern struct s_command *prog; +extern struct s_appends *appends; +extern regmatch_t *match; +extern size_t maxnsub; +extern u_long linenum; +extern size_t appendnum; +extern int aflag, eflag, nflag; +extern const char *fname, *outfname; +extern FILE *infile, *outfile; +extern int rflags; /* regex flags to use */ + +void cfclose(struct s_command *, struct s_command *); +void compile(void); +void cspace(SPACE *, const char *, size_t, enum e_spflag); +char *cu_fgets(char *, int, int *); +int mf_fgets(SPACE *, enum e_spflag); +int lastline(void); +void process(void); +void resetstate(void); +char *strregerror(int, regex_t *); +void *xmalloc(size_t); +void *xrealloc(void *, size_t); +void *xcalloc(size_t, size_t); diff --git a/usr.bin/sed/main.c b/usr.bin/sed/main.c new file mode 100644 index 0000000..cfe9d35 --- /dev/null +++ b/usr.bin/sed/main.c @@ -0,0 +1,517 @@ +/* $NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $ */ + +/*- + * Copyright (c) 2013 Johann 'Myrkraverk' Oskarsson. + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: main.c,v 1.34 2015/03/12 12:40:41 christos Exp $"); +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/usr.bin/sed/main.c 252231 2013-06-26 04:14:19Z pfg $"); +#endif + +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#endif + +#if 0 +static const char sccsid[] = "@(#)main.c 8.2 (Berkeley) 1/3/94"; +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#define _WITH_GETLINE +#include +#include +#include +#include + +#include "defs.h" +#include "extern.h" + +/* + * Linked list of units (strings and files) to be compiled + */ +struct s_compunit { + struct s_compunit *next; + enum e_cut {CU_FILE, CU_STRING} type; + char *s; /* Pointer to string or fname */ +}; + +/* + * Linked list pointer to compilation units and pointer to current + * next pointer. + */ +static struct s_compunit *script, **cu_nextp = &script; + +/* + * Linked list of files to be processed + */ +struct s_flist { + char *fname; + struct s_flist *next; +}; + +/* + * Linked list pointer to files and pointer to current + * next pointer. + */ +static struct s_flist *files, **fl_nextp = &files; + +FILE *infile; /* Current input file */ +FILE *outfile; /* Current output file */ + +int aflag, eflag, nflag; +int rflags = 0; +static int rval; /* Exit status */ + +static int ispan; /* Whether inplace editing spans across files */ + +/* + * Current file and line number; line numbers restart across compilation + * units, but span across input files. The latter is optional if editing + * in place. + */ +const char *fname; /* File name. */ +const char *outfname; /* Output file name */ +static char oldfname[PATH_MAX]; /* Old file name (for in-place editing) */ +static char tmpfname[PATH_MAX]; /* Temporary file name (for in-place editing) */ +static const char *inplace; /* Inplace edit file extension. */ +u_long linenum; + +static void add_compunit(enum e_cut, char *); +static void add_file(char *); +static void usage(void) __dead; + +int +main(int argc, char *argv[]) +{ + int c, fflag; + char *temp_arg; + + setprogname(argv[0]); + (void) setlocale(LC_ALL, ""); + + fflag = 0; + inplace = NULL; + + while ((c = getopt(argc, argv, "EI::ae:f:i::lnru")) != -1) + switch (c) { + case 'r': /* Gnu sed compat */ + case 'E': + rflags = REG_EXTENDED; + break; + case 'I': + inplace = optarg ? optarg : __UNCONST(""); + ispan = 1; /* span across input files */ + break; + case 'a': + aflag = 1; + break; + case 'e': + eflag = 1; + temp_arg = xmalloc(strlen(optarg) + 2); + strcpy(temp_arg, optarg); + strcat(temp_arg, "\n"); + add_compunit(CU_STRING, temp_arg); + break; + case 'f': + fflag = 1; + add_compunit(CU_FILE, optarg); + break; + case 'i': + inplace = optarg ? optarg : __UNCONST(""); + ispan = 0; /* don't span across input files */ + break; + case 'l': +#ifdef _IOLBF + c = setvbuf(stdout, NULL, _IOLBF, 0); +#else + c = setlinebuf(stdout); +#endif + if (c) + warn("setting line buffered output failed"); + break; + case 'n': + nflag = 1; + break; + case 'u': +#ifdef _IONBF + c = setvbuf(stdout, NULL, _IONBF, 0); +#else + c = -1; + errno = EOPNOTSUPP; +#endif + if (c) + warn("setting unbuffered output failed"); + break; + default: + case '?': + usage(); + } + argc -= optind; + argv += optind; + + /* First usage case; script is the first arg */ + if (!eflag && !fflag && *argv) { + add_compunit(CU_STRING, *argv); + argv++; + } + + compile(); + + /* Continue with first and start second usage */ + if (*argv) + for (; *argv; argv++) + add_file(*argv); + else + add_file(NULL); + process(); + cfclose(prog, NULL); + if (fclose(stdout)) + err(1, "stdout"); + exit(rval); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-aElnru] command [file ...]\n" + "\t%s [-aElnru] [-e command] [-f command_file] [-I[extension]]\n" + "\t [-i[extension]] [file ...]\n", getprogname(), getprogname()); + exit(1); +} + +/* + * Like fgets, but go through the chain of compilation units chaining them + * together. Empty strings and files are ignored. + */ +char * +cu_fgets(char *buf, int n, int *more) +{ + static enum {ST_EOF, ST_FILE, ST_STRING} state = ST_EOF; + static FILE *f; /* Current open file */ + static char *s; /* Current pointer inside string */ + static char string_ident[30]; + char *p; + +again: + switch (state) { + case ST_EOF: + if (script == NULL) { + if (more != NULL) + *more = 0; + return (NULL); + } + linenum = 0; + switch (script->type) { + case CU_FILE: + if ((f = fopen(script->s, "r")) == NULL) + err(1, "%s", script->s); + fname = script->s; + state = ST_FILE; + goto again; + case CU_STRING: + if (((size_t)snprintf(string_ident, + sizeof(string_ident), "\"%s\"", script->s)) >= + sizeof(string_ident) - 1) + (void)strcpy(string_ident + + sizeof(string_ident) - 6, " ...\""); + fname = string_ident; + s = script->s; + state = ST_STRING; + goto again; + } + case ST_FILE: + if ((p = fgets(buf, n, f)) != NULL) { + linenum++; + if (linenum == 1 && buf[0] == '#' && buf[1] == 'n') + nflag = 1; + if (more != NULL) + *more = !feof(f); + return (p); + } + script = script->next; + (void)fclose(f); + state = ST_EOF; + goto again; + case ST_STRING: + if (linenum == 0 && s[0] == '#' && s[1] == 'n') + nflag = 1; + p = buf; + for (;;) { + if (n-- <= 1) { + *p = '\0'; + linenum++; + if (more != NULL) + *more = 1; + return (buf); + } + switch (*s) { + case '\0': + state = ST_EOF; + if (s == script->s) { + script = script->next; + goto again; + } else { + script = script->next; + *p = '\0'; + linenum++; + if (more != NULL) + *more = 0; + return (buf); + } + case '\n': + *p++ = '\n'; + *p = '\0'; + s++; + linenum++; + if (more != NULL) + *more = 0; + return (buf); + default: + *p++ = *s++; + } + } + } + /* NOTREACHED */ + return (NULL); +} + +/* + * Like fgets, but go through the list of files chaining them together. + * Set len to the length of the line. + */ +int +mf_fgets(SPACE *sp, enum e_spflag spflag) +{ + struct stat sb; + size_t len; + static char *p = NULL; + static size_t plen = 0; + int c; + static int firstfile; + + if (infile == NULL) { + /* stdin? */ + if (files->fname == NULL) { + if (inplace != NULL) + errx(1, "-I or -i may not be used with stdin"); + infile = stdin; + fname = "stdin"; + outfile = stdout; + outfname = "stdout"; + } + firstfile = 1; + } + + for (;;) { + if (infile != NULL && (c = getc(infile)) != EOF) { + (void)ungetc(c, infile); + break; + } + /* If we are here then either eof or no files are open yet */ + if (infile == stdin) { + sp->len = 0; + return (0); + } + if (infile != NULL) { + fclose(infile); + if (*oldfname != '\0') { + /* if there was a backup file, remove it */ + unlink(oldfname); + /* + * Backup the original. Note that hard links + * are not supported on all filesystems. + */ + if ((link(fname, oldfname) != 0) && + (rename(fname, oldfname) != 0)) { + warn("rename()"); + if (*tmpfname) + unlink(tmpfname); + exit(1); + } + *oldfname = '\0'; + } + if (*tmpfname != '\0') { + if (outfile != NULL && outfile != stdout) + if (fclose(outfile) != 0) { + warn("fclose()"); + unlink(tmpfname); + exit(1); + } + outfile = NULL; + if (rename(tmpfname, fname) != 0) { + /* this should not happen really! */ + warn("rename()"); + unlink(tmpfname); + exit(1); + } + *tmpfname = '\0'; + } + outfname = NULL; + } + if (firstfile == 0) + files = files->next; + else + firstfile = 0; + if (files == NULL) { + sp->len = 0; + return (0); + } + fname = files->fname; + if (inplace != NULL) { + if (lstat(fname, &sb) != 0) + err(1, "%s", fname); + if (!(sb.st_mode & S_IFREG)) + errx(1, "%s: %s %s", fname, + "in-place editing only", + "works for regular files"); + if (*inplace != '\0') { + strlcpy(oldfname, fname, + sizeof(oldfname)); + len = strlcat(oldfname, inplace, + sizeof(oldfname)); + if (len > sizeof(oldfname)) + errx(1, "%s: name too long", fname); + } + char d_name[PATH_MAX], f_name[PATH_MAX]; + (void)strlcpy(d_name, fname, sizeof(d_name)); + (void)strlcpy(f_name, fname, sizeof(f_name)); + len = (size_t)snprintf(tmpfname, sizeof(tmpfname), + "%s/.!%ld!%s", dirname(d_name), (long)getpid(), + basename(f_name)); + if (len >= sizeof(tmpfname)) + errx(1, "%s: name too long", fname); + unlink(tmpfname); + if (outfile != NULL && outfile != stdout) + fclose(outfile); + if ((outfile = fopen(tmpfname, "w")) == NULL) + err(1, "%s", fname); + fchown(fileno(outfile), sb.st_uid, sb.st_gid); + fchmod(fileno(outfile), sb.st_mode & ALLPERMS); + outfname = tmpfname; + if (!ispan) { + linenum = 0; + resetstate(); + } + } else { + outfile = stdout; + outfname = "stdout"; + } + if ((infile = fopen(fname, "r")) == NULL) { + warn("%s", fname); + rval = 1; + continue; + } + } + /* + * We are here only when infile is open and we still have something + * to read from it. + * + * Use getline() so that we can handle essentially infinite input + * data. The p and plen are static so each invocation gives + * getline() the same buffer which is expanded as needed. + */ + ssize_t slen = getline(&p, &plen, infile); + if (slen == -1) + err(1, "%s", fname); + if (slen != 0 && p[slen - 1] == '\n') + slen--; + cspace(sp, p, (size_t)slen, spflag); + + linenum++; + + return (1); +} + +/* + * Add a compilation unit to the linked list + */ +static void +add_compunit(enum e_cut type, char *s) +{ + struct s_compunit *cu; + + cu = xmalloc(sizeof(struct s_compunit)); + cu->type = type; + cu->s = s; + cu->next = NULL; + *cu_nextp = cu; + cu_nextp = &cu->next; +} + +/* + * Add a file to the linked list + */ +static void +add_file(char *s) +{ + struct s_flist *fp; + + fp = xmalloc(sizeof(struct s_flist)); + fp->next = NULL; + *fl_nextp = fp; + fp->fname = s; + fl_nextp = &fp->next; +} + +int +lastline(void) +{ + int ch; + + if (files->next != NULL && (inplace == NULL || ispan)) + return (0); + if ((ch = getc(infile)) == EOF) + return (1); + ungetc(ch, infile); + return (0); +} diff --git a/usr.bin/sed/misc.c b/usr.bin/sed/misc.c new file mode 100644 index 0000000..bd0ecdf --- /dev/null +++ b/usr.bin/sed/misc.c @@ -0,0 +1,119 @@ +/* $NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $ */ + +/*- + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: misc.c,v 1.15 2014/06/26 02:14:32 christos Exp $"); +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/usr.bin/sed/misc.c 200462 2009-12-13 03:14:06Z delphij $"); +#endif + +#if 0 +static const char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93"; +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "extern.h" + +/* + * malloc with result test + */ +void * +xmalloc(size_t size) +{ + void *p; + + if ((p = malloc(size)) == NULL) + err(1, "malloc(%zu)", size); + return p; +} + +/* + * realloc with result test + */ +void * +xrealloc(void *p, size_t size) +{ + if (p == NULL) /* Compatibility hack. */ + return (xmalloc(size)); + + if ((p = realloc(p, size)) == NULL) + err(1, "realloc(%zu)", size); + return p; +} + +/* + * realloc with result test + */ +void * +xcalloc(size_t c, size_t n) +{ + void *p; + + if ((p = calloc(c, n)) == NULL) + err(1, "calloc(%zu, %zu)", c, n); + return p; +} +/* + * Return a string for a regular expression error passed. This is overkill, + * because of the silly semantics of regerror (we can never know the size of + * the buffer). + */ +char * +strregerror(int errcode, regex_t *preg) +{ + char buf[1]; + static char *oe; + size_t s; + + if (oe != NULL) + free(oe); + s = regerror(errcode, preg, buf, 0); + oe = xmalloc(s); + (void)regerror(errcode, preg, oe, s); + return (oe); +} diff --git a/usr.bin/sed/process.c b/usr.bin/sed/process.c new file mode 100644 index 0000000..896df5a --- /dev/null +++ b/usr.bin/sed/process.c @@ -0,0 +1,792 @@ +/* $NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $ */ + +/*- + * Copyright (c) 1992 Diomidis Spinellis. + * Copyright (c) 1992, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Diomidis Spinellis of Imperial College, University of London. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +__RCSID("$NetBSD: process.c,v 1.52 2015/03/12 12:40:41 christos Exp $"); +#ifdef __FBSDID +__FBSDID("$FreeBSD: head/usr.bin/sed/process.c 192732 2009-05-25 06:45:33Z brian $"); +#endif + +#if 0 +static const char sccsid[] = "@(#)process.c 8.6 (Berkeley) 4/20/94"; +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "defs.h" +#include "extern.h" + +static SPACE HS, PS, SS, YS; +#define pd PS.deleted +#define ps PS.space +#define psl PS.len +#define hs HS.space +#define hsl HS.len + +static __inline int applies(struct s_command *); +static void do_tr(struct s_tr *); +static void flush_appends(void); +static void lputs(char *, size_t); +static __inline int regexec_e(regex_t *, const char *, int, int, size_t); +static void regsub(SPACE *, char *, char *); +static int substitute(struct s_command *); + +struct s_appends *appends; /* Array of pointers to strings to append. */ +static size_t appendx; /* Index into appends array. */ +size_t appendnum; /* Size of appends array. */ + +static int lastaddr; /* Set by applies if last address of a range. */ +static int sdone; /* If any substitutes since last line input. */ + /* Iov structure for 'w' commands. */ +static regex_t *defpreg; +size_t maxnsub; +regmatch_t *match; + +#define OUT() do {fwrite(ps, 1, psl, outfile); fputc('\n', outfile);} while (0) + +void +process(void) +{ + struct s_command *cp; + SPACE tspace; + size_t oldpsl = 0; + char *p; + + p = NULL; + + for (linenum = 0; mf_fgets(&PS, REPLACE);) { + pd = 0; +top: + cp = prog; +redirect: + while (cp != NULL) { + if (!applies(cp)) { + cp = cp->next; + continue; + } + switch (cp->code) { + case '{': + cp = cp->u.c; + goto redirect; + case 'a': + if (appendx >= appendnum) + appends = xrealloc(appends, + sizeof(struct s_appends) * + (appendnum *= 2)); + appends[appendx].type = AP_STRING; + appends[appendx].s = cp->t; + appends[appendx].len = strlen(cp->t); + appendx++; + break; + case 'b': + cp = cp->u.c; + goto redirect; + case 'c': + pd = 1; + psl = 0; + if (cp->a2 == NULL || lastaddr || lastline()) + (void)fprintf(outfile, "%s", cp->t); + goto new; + case 'd': + pd = 1; + goto new; + case 'D': + if (pd) + goto new; + if (psl == 0 || + (p = memchr(ps, '\n', psl - 1)) == NULL) { + pd = 1; + goto new; + } else { + psl -= (size_t)((p + 1) - ps); + memmove(ps, p + 1, psl); + goto top; + } + case 'g': + cspace(&PS, hs, hsl, REPLACE); + break; + case 'G': + cspace(&PS, "\n", 1, APPEND); + cspace(&PS, hs, hsl, APPEND); + break; + case 'h': + cspace(&HS, ps, psl, REPLACE); + break; + case 'H': + cspace(&HS, "\n", 1, APPEND); + cspace(&HS, ps, psl, APPEND); + break; + case 'i': + (void)fprintf(outfile, "%s", cp->t); + break; + case 'l': + lputs(ps, psl); + break; + case 'n': + if (!nflag && !pd) + OUT(); + flush_appends(); + if (!mf_fgets(&PS, REPLACE)) + exit(0); + pd = 0; + break; + case 'N': + flush_appends(); + cspace(&PS, "\n", 1, APPEND); + if (!mf_fgets(&PS, APPEND)) + exit(0); + break; + case 'p': + if (pd) + break; + OUT(); + break; + case 'P': + if (pd) + break; + if ((p = memchr(ps, '\n', psl - 1)) != NULL) { + oldpsl = psl; + psl = (size_t)(p - ps); + } + OUT(); + if (p != NULL) + psl = oldpsl; + break; + case 'q': + if (!nflag && !pd) + OUT(); + flush_appends(); + exit(0); + case 'r': + if (appendx >= appendnum) + appends = xrealloc(appends, + sizeof(struct s_appends) * + (appendnum *= 2)); + appends[appendx].type = AP_FILE; + appends[appendx].s = cp->t; + appends[appendx].len = strlen(cp->t); + appendx++; + break; + case 's': + sdone |= substitute(cp); + break; + case 't': + if (sdone) { + sdone = 0; + cp = cp->u.c; + goto redirect; + } + break; + case 'w': + if (pd) + break; + if (cp->u.fd == -1 && (cp->u.fd = open(cp->t, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, + DEFFILEMODE)) == -1) + err(1, "%s", cp->t); + if (write(cp->u.fd, ps, psl) != (ssize_t)psl || + write(cp->u.fd, "\n", 1) != 1) + err(1, "%s", cp->t); + break; + case 'x': + /* + * If the hold space is null, make it empty + * but not null. Otherwise the pattern space + * will become null after the swap, which is + * an abnormal condition. + */ + if (hs == NULL) + cspace(&HS, "", 0, REPLACE); + tspace = PS; + PS = HS; + HS = tspace; + break; + case 'y': + if (pd || psl == 0) + break; + do_tr(cp->u.y); + break; + case ':': + case '}': + break; + case '=': + (void)fprintf(outfile, "%lu\n", linenum); + } + cp = cp->next; + } /* for all cp */ + +new: if (!nflag && !pd) + OUT(); + flush_appends(); + } /* for all lines */ +} + +/* + * TRUE if the address passed matches the current program state + * (lastline, linenumber, ps). + */ +#define MATCH(a) \ + ((a)->type == AT_RE ? regexec_e((a)->u.r, ps, 0, 1, psl) : \ + (a)->type == AT_LINE ? linenum == (a)->u.l : lastline()) + +/* + * Return TRUE if the command applies to the current line. Sets the start + * line for process ranges. Interprets the non-select (``!'') flag. + */ +static __inline int +applies(struct s_command *cp) +{ + int r; + + lastaddr = 0; + if (cp->a1 == NULL && cp->a2 == NULL) + r = 1; + else if (cp->a2) + if (cp->startline > 0) { + switch (cp->a2->type) { + case AT_RELLINE: + if (linenum - cp->startline <= cp->a2->u.l) + r = 1; + else { + cp->startline = 0; + r = 0; + } + break; + default: + if (MATCH(cp->a2)) { + cp->startline = 0; + lastaddr = 1; + r = 1; + } else if (cp->a2->type == AT_LINE && + linenum > cp->a2->u.l) { + /* + * We missed the 2nd address due to a + * branch, so just close the range and + * return false. + */ + cp->startline = 0; + r = 0; + } else + r = 1; + } + } else if (cp->a1 && MATCH(cp->a1)) { + /* + * If the second address is a number less than or + * equal to the line number first selected, only + * one line shall be selected. + * -- POSIX 1003.2 + * Likewise if the relative second line address is zero. + */ + if ((cp->a2->type == AT_LINE && + linenum >= cp->a2->u.l) || + (cp->a2->type == AT_RELLINE && cp->a2->u.l == 0)) + lastaddr = 1; + else { + cp->startline = linenum; + } + r = 1; + } else + r = 0; + else + r = MATCH(cp->a1); + return (cp->nonsel ? ! r : r); +} + +/* + * Reset the sed processor to its initial state. + */ +void +resetstate(void) +{ + struct s_command *cp; + + /* + * Reset all in-range markers. + */ + for (cp = prog; cp; cp = cp->code == '{' ? cp->u.c : cp->next) + if (cp->a2) + cp->startline = 0; + + /* + * Clear out the hold space. + */ + cspace(&HS, "", 0, REPLACE); +} + +/* + * substitute -- + * Do substitutions in the pattern space. Currently, we build a + * copy of the new pattern space in the substitute space structure + * and then swap them. + */ +static int +substitute(struct s_command *cp) +{ + SPACE tspace; + regex_t *re; + regoff_t re_off, slen; + int lastempty, n; + char *s; + + s = ps; + re = cp->u.s->re; + if (re == NULL) { + if (defpreg != NULL && cp->u.s->maxbref > defpreg->re_nsub) { + linenum = cp->u.s->linenum; + errx(1, "%lu: %s: \\%u not defined in the RE", + linenum, fname, cp->u.s->maxbref); + } + } + if (!regexec_e(re, s, 0, 0, psl)) + return (0); + + SS.len = 0; /* Clean substitute space. */ + slen = (regoff_t)psl; + n = cp->u.s->n; + lastempty = 1; + + switch (n) { + case 0: /* Global */ + do { + if (lastempty || match[0].rm_so != match[0].rm_eo) { + /* Locate start of replaced string. */ + re_off = match[0].rm_so; + /* Copy leading retained string. */ + cspace(&SS, s, (size_t)re_off, APPEND); + /* Add in regular expression. */ + regsub(&SS, s, cp->u.s->new); + } + + /* Move past this match. */ + if (match[0].rm_so != match[0].rm_eo) { + s += match[0].rm_eo; + slen -= match[0].rm_eo; + lastempty = 0; + } else { + if (match[0].rm_so < slen) + cspace(&SS, s + match[0].rm_so, 1, + APPEND); + s += match[0].rm_so + 1; + slen -= match[0].rm_so + 1; + lastempty = 1; + } + } while (slen >= 0 && regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen)); + /* Copy trailing retained string. */ + if (slen > 0) + cspace(&SS, s, (size_t)slen, APPEND); + break; + default: /* Nth occurrence */ + while (--n) { + if (match[0].rm_eo == match[0].rm_so) + match[0].rm_eo = match[0].rm_so + 1; + s += match[0].rm_eo; + slen -= match[0].rm_eo; + if (slen < 0) + return (0); + if (!regexec_e(re, s, REG_NOTBOL, 0, (size_t)slen)) + return (0); + } + /* FALLTHROUGH */ + case 1: /* 1st occurrence */ + /* Locate start of replaced string. */ + re_off = match[0].rm_so + (s - ps); + /* Copy leading retained string. */ + cspace(&SS, ps, (size_t)re_off, APPEND); + /* Add in regular expression. */ + regsub(&SS, s, cp->u.s->new); + /* Copy trailing retained string. */ + s += match[0].rm_eo; + slen -= match[0].rm_eo; + cspace(&SS, s, (size_t)slen, APPEND); + break; + } + + /* + * Swap the substitute space and the pattern space, and make sure + * that any leftover pointers into stdio memory get lost. + */ + tspace = PS; + PS = SS; + SS = tspace; + SS.space = SS.back; + + /* Handle the 'p' flag. */ + if (cp->u.s->p) + OUT(); + + /* Handle the 'w' flag. */ + if (cp->u.s->wfile && !pd) { + if (cp->u.s->wfd == -1 && (cp->u.s->wfd = open(cp->u.s->wfile, + O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, DEFFILEMODE)) == -1) + err(1, "%s", cp->u.s->wfile); + if (write(cp->u.s->wfd, ps, psl) != (ssize_t)psl || + write(cp->u.s->wfd, "\n", 1) != 1) + err(1, "%s", cp->u.s->wfile); + } + return (1); +} + +/* + * do_tr -- + * Perform translation ('y' command) in the pattern space. + */ +static void +do_tr(struct s_tr *y) +{ + SPACE tmp; + char c, *p; + size_t clen, left; + size_t i; + + if (MB_CUR_MAX == 1) { + /* + * Single-byte encoding: perform in-place translation + * of the pattern space. + */ + for (p = ps; p < &ps[psl]; p++) + *p = (char)y->bytetab[(u_char)*p]; + } else { + /* + * Multi-byte encoding: perform translation into the + * translation space, then swap the translation and + * pattern spaces. + */ + /* Clean translation space. */ + YS.len = 0; + for (p = ps, left = psl; left > 0; p += clen, left -= clen) { + if ((c = (char)y->bytetab[(u_char)*p]) != '\0') { + cspace(&YS, &c, 1, APPEND); + clen = 1; + continue; + } + for (i = 0; i < y->nmultis; i++) + if (left >= y->multis[i].fromlen && + memcmp(p, y->multis[i].from, + y->multis[i].fromlen) == 0) + break; + if (i < y->nmultis) { + cspace(&YS, y->multis[i].to, + y->multis[i].tolen, APPEND); + clen = y->multis[i].fromlen; + } else { + cspace(&YS, p, 1, APPEND); + clen = 1; + } + } + /* Swap the translation space and the pattern space. */ + tmp = PS; + PS = YS; + YS = tmp; + YS.space = YS.back; + } +} + +/* + * Flush append requests. Always called before reading a line, + * therefore it also resets the substitution done (sdone) flag. + */ +static void +flush_appends(void) +{ + FILE *f; + size_t count, i; + char buf[8 * 1024]; + + for (i = 0; i < appendx; i++) + switch (appends[i].type) { + case AP_STRING: + fwrite(appends[i].s, sizeof(char), appends[i].len, + outfile); + break; + case AP_FILE: + /* + * Read files probably shouldn't be cached. Since + * it's not an error to read a non-existent file, + * it's possible that another program is interacting + * with the sed script through the filesystem. It + * would be truly bizarre, but possible. It's probably + * not that big a performance win, anyhow. + */ + if ((f = fopen(appends[i].s, "r")) == NULL) + break; + while ((count = fread(buf, sizeof(char), sizeof(buf), f))) + (void)fwrite(buf, sizeof(char), count, outfile); + (void)fclose(f); + break; + } + if (ferror(outfile)) + errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO)); + appendx = 0; + sdone = 0; +} + +static void +lputs(char *s, size_t len) +{ + static const char escapes[] = "\\\a\b\f\r\t\v"; + int c; + size_t col, width; + const char *p; +#ifdef TIOCGWINSZ + struct winsize win; +#endif + static size_t termwidth = (size_t)-1; + size_t clen, i; + wchar_t wc; + mbstate_t mbs; + + if (outfile != stdout) + termwidth = 60; + if (termwidth == (size_t)-1) { + if ((p = getenv("COLUMNS")) && *p != '\0') + termwidth = (size_t)atoi(p); +#ifdef TIOCGWINSZ + else if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &win) == 0 && + win.ws_col > 0) + termwidth = win.ws_col; +#endif + else + termwidth = 60; + } + if (termwidth == 0) + termwidth = 1; + + memset(&mbs, 0, sizeof(mbs)); + col = 0; + while (len != 0) { + clen = mbrtowc(&wc, s, len, &mbs); + if (clen == 0) + clen = 1; + if (clen == (size_t)-1 || clen == (size_t)-2) { + wc = (unsigned char)*s; + clen = 1; + memset(&mbs, 0, sizeof(mbs)); + } + if (wc == '\n') { + if (col + 1 >= termwidth) + fprintf(outfile, "\\\n"); + fputc('$', outfile); + fputc('\n', outfile); + col = 0; + } else if (iswprint(wc)) { + width = (size_t)wcwidth(wc); + if (col + width >= termwidth) { + fprintf(outfile, "\\\n"); + col = 0; + } + fwrite(s, 1, clen, outfile); + col += width; + } else if (wc != L'\0' && (c = wctob(wc)) != EOF && + (p = strchr(escapes, c)) != NULL) { + if (col + 2 >= termwidth) { + fprintf(outfile, "\\\n"); + col = 0; + } + fprintf(outfile, "\\%c", "\\abfrtv"[p - escapes]); + col += 2; + } else { + if (col + 4 * clen >= termwidth) { + fprintf(outfile, "\\\n"); + col = 0; + } + for (i = 0; i < clen; i++) + fprintf(outfile, "\\%03o", + (int)(unsigned char)s[i]); + col += 4 * clen; + } + s += clen; + len -= clen; + } + if (col + 1 >= termwidth) + fprintf(outfile, "\\\n"); + (void)fputc('$', outfile); + (void)fputc('\n', outfile); + if (ferror(outfile)) + errx(1, "%s: %s", outfname, strerror(errno ? errno : EIO)); +} + +static __inline int +regexec_e(regex_t *preg, const char *string, int eflags, int nomatch, + size_t slen) +{ + int eval; +#ifndef REG_STARTEND + char *buf; +#endif + + if (preg == NULL) { + if (defpreg == NULL) + errx(1, "first RE may not be empty"); + } else + defpreg = preg; + + /* Set anchors */ +#ifndef REG_STARTEND + buf = xmalloc(slen + 1); + (void)memcpy(buf, string, slen); + buf[slen] = '\0'; + eval = regexec(defpreg, buf, + nomatch ? 0 : maxnsub + 1, match, eflags); + free(buf); +#else + match[0].rm_so = 0; + match[0].rm_eo = (regoff_t)slen; + eval = regexec(defpreg, string, + nomatch ? 0 : maxnsub + 1, match, eflags | REG_STARTEND); +#endif + switch(eval) { + case 0: + return (1); + case REG_NOMATCH: + return (0); + } + errx(1, "RE error: %s", strregerror(eval, defpreg)); + /* NOTREACHED */ +} + +/* + * regsub - perform substitutions after a regexp match + * Based on a routine by Henry Spencer + */ +static void +regsub(SPACE *sp, char *string, char *src) +{ + size_t len; + int no; + char c, *dst; + +#define NEEDSP(reqlen) \ + /* XXX What is the +1 for? */ \ + if (sp->len + (reqlen) + 1 >= sp->blen) { \ + sp->blen += (reqlen) + 1024; \ + sp->space = sp->back = xrealloc(sp->back, sp->blen); \ + dst = sp->space + sp->len; \ + } + + dst = sp->space + sp->len; + while ((c = *src++) != '\0') { + if (c == '&') + no = 0; + else if (c == '\\' && isdigit((unsigned char)*src)) + no = *src++ - '0'; + else + no = -1; + if (no < 0) { /* Ordinary character. */ + if (c == '\\' && (*src == '\\' || *src == '&')) + c = *src++; + NEEDSP(1); + *dst++ = c; + ++sp->len; + } else if (match[no].rm_so != -1 && match[no].rm_eo != -1) { + len = (size_t)(match[no].rm_eo - match[no].rm_so); + NEEDSP(len); + memmove(dst, string + match[no].rm_so, len); + dst += len; + sp->len += len; + } + } + NEEDSP(1); + *dst = '\0'; +} + +/* + * cspace -- + * Concatenate space: append the source space to the destination space, + * allocating new space as necessary. + */ +void +cspace(SPACE *sp, const char *p, size_t len, enum e_spflag spflag) +{ + size_t tlen; + + /* Make sure SPACE has enough memory and ramp up quickly. */ + tlen = sp->len + len + 1; + if (tlen > sp->blen) { + sp->blen = tlen + 1024; + sp->space = sp->back = xrealloc(sp->back, sp->blen); + } + + if (spflag == REPLACE) + sp->len = 0; + + memmove(sp->space + sp->len, p, len); + + sp->space[sp->len += len] = '\0'; +} + +/* + * Close all cached opened files and report any errors + */ +void +cfclose(struct s_command *cp, struct s_command *end) +{ + + for (; cp != end; cp = cp->next) + switch(cp->code) { + case 's': + if (cp->u.s->wfd != -1 && close(cp->u.s->wfd)) + err(1, "%s", cp->u.s->wfile); + cp->u.s->wfd = -1; + break; + case 'w': + if (cp->u.fd != -1 && close(cp->u.fd)) + err(1, "%s", cp->t); + cp->u.fd = -1; + break; + case '{': + cfclose(cp->u.c, cp->next); + break; + } +} diff --git a/usr.bin/sed/sed.1 b/usr.bin/sed/sed.1 new file mode 100644 index 0000000..82b3cbd --- /dev/null +++ b/usr.bin/sed/sed.1 @@ -0,0 +1,640 @@ +.\" $NetBSD: sed.1,v 1.40 2014/06/25 02:05:58 uwe Exp $ +.\" Copyright (c) 1992, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)sed.1 8.2 (Berkeley) 12/30/93 +.\" $FreeBSD: head/usr.bin/sed/sed.1 259132 2013-12-09 18:57:20Z eadler $ +.\" +.Dd June 18, 2014 +.Dt SED 1 +.Os +.Sh NAME +.Nm sed +.Nd stream editor +.Sh SYNOPSIS +.Nm +.Op Fl aElnru +.Ar command +.Op Ar +.Nm +.Op Fl aElnru +.Op Fl e Ar command +.Op Fl f Ar command_file +.Op Fl I Ns Op Ar extension +.Op Fl i Ns Op Ar extension +.Op Ar +.Sh DESCRIPTION +The +.Nm +utility reads the specified files, or the standard input if no files +are specified, modifying the input as specified by a list of commands. +The input is then written to the standard output. +.Pp +A single command may be specified as the first argument to +.Nm . +Multiple commands may be specified by using the +.Fl e +or +.Fl f +options. +All commands are applied to the input in the order they are specified +regardless of their origin. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl a +The files listed as parameters for the +.Dq w +functions are created (or truncated) before any processing begins, +by default. +The +.Fl a +option causes +.Nm +to delay opening each file until a command containing the related +.Dq w +function is applied to a line of input. +.It Fl E +Interpret regular expressions as extended (modern) regular expressions +rather than basic regular expressions (BRE's). +The +.Xr re_format 7 +manual page fully describes both formats. +.It Fl e Ar command +Append the editing commands specified by the +.Ar command +argument +to the list of commands. +.It Fl f Ar command_file +Append the editing commands found in the file +.Ar command_file +to the list of commands. +The editing commands should each be listed on a separate line. +.It Fl I Ns Op Ar extension +Edit files in-place, saving backups with the specified +.Ar extension . +If no +.Ar extension +is given, no backup will be saved. +It is not recommended to give a zero-length +.Ar extension +when in-place editing files, as you risk corruption or partial content +in situations where disk space is exhausted, etc. +.Pp +Note that in-place editing with +.Fl I +still takes place in a single continuous line address space covering +all files, although each file preserves its individuality instead of +forming one output stream. +The line counter is never reset between files, address ranges can span +file boundaries, and the +.Dq $ +address matches only the last line of the last file. +(See +.Sx "Sed Addresses" . ) +That can lead to unexpected results in many cases of in-place editing, +where using +.Fl i +is desired. +.It Fl i Ns Op Ar extension +Edit files in-place similarly to +.Fl I , +but treat each file independently from other files. +In particular, line numbers in each file start at 1, +the +.Dq $ +address matches the last line of the current file, +and address ranges are limited to the current file. +(See +.Sx "Sed Addresses" . ) +The net result is as though each file were edited by a separate +.Nm +instance. +.It Fl l +Make output line buffered. +.It Fl n +By default, each line of input is echoed to the standard output after +all of the commands have been applied to it. +The +.Fl n +option suppresses this behavior. +.It Fl r +Same as +.Fl E +for compatibility with GNU sed. +.It Fl u +Make output unbuffered. +.El +.Pp +The form of a +.Nm +command is as follows: +.Pp +.Dl [address[,address]]function[arguments] +.Pp +Whitespace may be inserted before the first address and the function +portions of the command. +.Pp +Normally, +.Nm +cyclically copies a line of input, not including its terminating newline +character, into a +.Em "pattern space" , +(unless there is something left after a +.Dq D +function), +applies all of the commands with addresses that select that pattern space, +copies the pattern space to the standard output, appending a newline, and +deletes the pattern space. +.Pp +Some of the functions use a +.Em "hold space" +to save all or part of the pattern space for subsequent retrieval. +.Ss "Sed Addresses" +An address is not required, but if specified must have one of the +following formats: +.Bl -bullet -offset indent +.It +a number that counts +input lines +cumulatively across input files (or in each file independently +if a +.Fl i +option is in effect); +.It +a dollar +.Pq Dq $ +character that addresses the last line of input (or the last line +of the current file if a +.Fl i +option was specified); +.It +a context address +that consists of a regular expression preceded and followed by a +delimiter. +The closing delimiter can also optionally be followed by the +.Dq i +character, to indicate that the regular expression is to be matched +in a case-insensitive way. +.El +.Pp +A command line with no addresses selects every pattern space. +.Pp +A command line with one address selects all of the pattern spaces +that match the address. +.Pp +A command line with two addresses selects an inclusive range. +This +range starts with the first pattern space that matches the first +address. +The end of the range is the next following pattern space +that matches the second address. +If the second address is a number +less than or equal to the line number first selected, only that +line is selected. +The number in the second address may be prefixed with a +.Pq Dq \&+ +to specify the number of lines to match after the first pattern. +In the case when the second address is a context +address, +.Nm +does not re-match the second address against the +pattern space that matched the first address. +Starting at the +first line following the selected range, +.Nm +starts looking again for the first address. +.Pp +Editing commands can be applied to non-selected pattern spaces by use +of the exclamation character +.Pq Dq \&! +function. +.Ss "Sed Regular Expressions" +The regular expressions used in +.Nm , +by default, are basic regular expressions (BREs, see +.Xr re_format 7 +for more information), but extended (modern) regular expressions can be used +instead if the +.Fl E +flag is given. +In addition, +.Nm +has the following two additions to regular expressions: +.Pp +.Bl -enum -compact +.It +In a context address, any character other than a backslash +.Pq Dq \e +or newline character may be used to delimit the regular expression. +The opening delimiter needs to be preceded by a backslash +unless it is a slash. +For example, the context address +.Li \exabcx +is equivalent to +.Li /abc/ . +Also, putting a backslash character before the delimiting character +within the regular expression causes the character to be treated literally. +For example, in the context address +.Li \exabc\exdefx , +the RE delimiter is an +.Dq x +and the second +.Dq x +stands for itself, so that the regular expression is +.Dq abcxdef . +.Pp +.It +The escape sequence \en matches a newline character embedded in the +pattern space. +You cannot, however, use a literal newline character in an address or +in the substitute command. +.El +.Pp +One special feature of +.Nm +regular expressions is that they can default to the last regular +expression used. +If a regular expression is empty, i.e., just the delimiter characters +are specified, the last regular expression encountered is used instead. +The last regular expression is defined as the last regular expression +used as part of an address or substitute command, and at run-time, not +compile-time. +For example, the command +.Dq /abc/s//XXX/ +will substitute +.Dq XXX +for the pattern +.Dq abc . +.Ss "Sed Functions" +In the following list of commands, the maximum number of permissible +addresses for each command is indicated by [0addr], [1addr], or [2addr], +representing zero, one, or two addresses. +.Pp +The argument +.Em text +consists of one or more lines. +To embed a newline in the text, precede it with a backslash. +Other backslashes in text are deleted and the following character +taken literally. +.Pp +The +.Dq r +and +.Dq w +functions take an optional file parameter, which should be separated +from the function letter by white space. +Each file given as an argument to +.Nm +is created (or its contents truncated) before any input processing begins. +.Pp +The +.Dq b , +.Dq r , +.Dq s , +.Dq t , +.Dq w , +.Dq y , +.Dq \&! , +and +.Dq \&: +functions all accept additional arguments. +The following synopses indicate which arguments have to be separated from +the function letters by white space characters. +.Pp +Two of the functions take a function-list. +This is a list of +.Nm +functions separated by newlines, as follows: +.Bd -literal -offset indent +{ function + function + ... + function +} +.Ed +.Pp +The +.Dq { +can be preceded by white space and can be followed by white space. +The function can be preceded by white space. +The terminating +.Dq } +must be preceded by a newline, and may also be preceded by white space. +.Pp +.Bl -tag -width "XXXXXX" -compact +.It [2addr] function-list +Execute function-list only when the pattern space is selected. +.Pp +.It [1addr]a\e +.It text +Write +.Em text +to standard output immediately before each attempt to read a line of input, +whether by executing the +.Dq N +function or by beginning a new cycle. +.Pp +.It [2addr]b[label] +Branch to the +.Dq \&: +function with the specified label. +If the label is not specified, branch to the end of the script. +.Pp +.It [2addr]c\e +.It text +Delete the pattern space. +With 0 or 1 address or at the end of a 2-address range, +.Em text +is written to the standard output. +.Pp +.It [2addr]d +Delete the pattern space and start the next cycle. +.Pp +.It [2addr]D +Delete the initial segment of the pattern space through the first +newline character and start the next cycle. +.Pp +.It [2addr]g +Replace the contents of the pattern space with the contents of the +hold space. +.Pp +.It [2addr]G +Append a newline character followed by the contents of the hold space +to the pattern space. +.Pp +.It [2addr]h +Replace the contents of the hold space with the contents of the +pattern space. +.Pp +.It [2addr]H +Append a newline character followed by the contents of the pattern space +to the hold space. +.Pp +.It [1addr]i\e +.It text +Write +.Em text +to the standard output. +.Pp +.It [2addr]l +(The letter ell.) +Write the pattern space to the standard output in a visually unambiguous +form. +This form is as follows: +.Pp +.Bl -tag -width "carriage-returnXX" -offset indent -compact +.It backslash +\e\e +.It alert +\ea +.It form-feed +\ef +.It carriage-return +\er +.It tab +\et +.It vertical tab +\ev +.El +.Pp +Nonprintable characters are written as three-digit octal numbers (with a +preceding backslash) for each byte in the character (most significant byte +first). +Long lines are folded, with the point of folding indicated by displaying +a backslash followed by a newline. +The end of each line is marked with a +.Dq $ . +.Pp +.It [2addr]n +Write the pattern space to the standard output if the default output has +not been suppressed, and replace the pattern space with the next line of +input. +.Pp +.It [2addr]N +Append the next line of input to the pattern space, using an embedded +newline character to separate the appended material from the original +contents. +Note that the current line number changes. +.Pp +.It [2addr]p +Write the pattern space to standard output. +.Pp +.It [2addr]P +Write the pattern space, up to the first newline character to the +standard output. +.Pp +.It [1addr]q +Branch to the end of the script and quit without starting a new cycle. +.Pp +.It [1addr]r file +Copy the contents of +.Em file +to the standard output immediately before the next attempt to read a +line of input. +If +.Em file +cannot be read for any reason, it is silently ignored and no error +condition is set. +.Pp +.It [2addr]s/regular expression/replacement/flags +Substitute the replacement string for the first instance of the regular +expression in the pattern space. +Any character other than backslash or newline can be used instead of +a slash to delimit the RE and the replacement. +Within the RE and the replacement, the RE delimiter itself can be used as +a literal character if it is preceded by a backslash. +.Pp +An ampersand +.Pq Dq & +appearing in the replacement is replaced by the string matching the RE. +The special meaning of +.Dq & +in this context can be suppressed by preceding it by a backslash. +The string +.Dq \e# , +where +.Dq # +is a digit, is replaced by the text matched +by the corresponding backreference expression (see +.Xr re_format 7 ) . +.Pp +A line can be split by substituting a newline character into it. +To specify a newline character in the replacement string, precede it with +a backslash. +.Pp +The value of +.Em flags +in the substitute function is zero or more of the following: +.Bl -tag -width "XXXXXX" -offset indent +.It Ar N +Make the substitution only for the +.Ar N Ns 'th +occurrence of the regular expression in the pattern space. +.It g +Make the substitution for all non-overlapping matches of the +regular expression, not just the first one. +.It p +Write the pattern space to standard output if a replacement was made. +If the replacement string is identical to that which it replaces, it +is still considered to have been a replacement. +.It w Em file +Append the pattern space to +.Em file +if a replacement was made. +If the replacement string is identical to that which it replaces, it +is still considered to have been a replacement. +.It i or I +Match the regular expression in a case-insensitive way. +.El +.Pp +.It [2addr]t [label] +Branch to the +.Dq \&: +function bearing the label if any substitutions have been made since the +most recent reading of an input line or execution of a +.Dq t +function. +If no label is specified, branch to the end of the script. +.Pp +.It [2addr]w Em file +Append the pattern space to the +.Em file . +.Pp +.It [2addr]x +Swap the contents of the pattern and hold spaces. +.Pp +.It [2addr]y/string1/string2/ +Replace all occurrences of characters in +.Em string1 +in the pattern space with the corresponding characters from +.Em string2 . +Any character other than a backslash or newline can be used instead of +a slash to delimit the strings. +Within +.Em string1 +and +.Em string2 , +a backslash followed by any character other than a newline is that literal +character, and a backslash followed by an ``n'' is replaced by a newline +character. +.Pp +.It [2addr]!function +.It [2addr]!function-list +Apply the function or function-list only to the lines that are +.Em not +selected by the address(es). +.Pp +.It [0addr]:label +This function does nothing; it bears a label to which the +.Dq b +and +.Dq t +commands may branch. +.Pp +.It [1addr]= +Write the line number to the standard output followed by a newline +character. +.Pp +.It [0addr] +Empty lines are ignored. +.Pp +.It [0addr]# +The +.Dq # +and the remainder of the line are ignored (treated as a comment), with +the single exception that if the first two characters in the file are +.Dq #n , +the default output is suppressed. +This is the same as specifying the +.Fl n +option on the command line. +.El +.Sh ENVIRONMENT +The +.Ev COLUMNS , LANG , LC_ALL , LC_CTYPE +and +.Ev LC_COLLATE +environment variables affect the execution of +.Nm +as described in +.Xr environ 7 . +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr awk 1 , +.Xr ed 1 , +.Xr grep 1 , +.Xr regex 3 , +.Xr re_format 7 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2 +specification. +.Pp +The +.Fl a , E , I , +and +.Fl i +options, the prefixing +.Dq \&+ +in the second member of an address range, +as well as the +.Dq I +flag to the address regular expression and substitution command are +non-standard +.Fx +extensions and may not be available on other operating systems. +.Sh HISTORY +A +.Nm +command, written by +.An L. E. McMahon , +appeared in +.At v7 . +.Sh AUTHORS +.An "Diomidis D. Spinellis" Aq dds@FreeBSD.org +.Sh BUGS +Multibyte characters containing a byte with value 0x5C +.Tn ( ASCII +.Ql \e ) +may be incorrectly treated as line continuation characters in arguments to the +.Dq a , +.Dq c +and +.Dq i +commands. +Multibyte characters cannot be used as delimiters with the +.Dq s +and +.Dq y +commands. diff --git a/usr.bin/sort/append.c b/usr.bin/sort/append.c new file mode 100644 index 0000000..07e15e1 --- /dev/null +++ b/usr.bin/sort/append.c @@ -0,0 +1,94 @@ +/* $NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sort.h" + +__RCSID("$NetBSD: append.c,v 1.23 2009/11/06 18:34:22 joerg Exp $"); + +#include + +/* + * copy sorted lines to output + * Ignore duplicates (marked with -ve keylen) + */ +void +append(RECHEADER **keylist, int nelem, FILE *fp, put_func_t put) +{ + RECHEADER **cpos, **lastkey; + RECHEADER *crec; + + lastkey = keylist + nelem; + if (REVERSE) { + for (cpos = lastkey; cpos-- > keylist;) { + crec = *cpos; + if (crec->keylen >= 0) + put(crec, fp); + } + } else { + for (cpos = keylist; cpos < lastkey; cpos++) { + crec = *cpos; + if (crec->keylen >= 0) + put(crec, fp); + } + } +} diff --git a/usr.bin/sort/fields.c b/usr.bin/sort/fields.c new file mode 100644 index 0000000..6208a78 --- /dev/null +++ b/usr.bin/sort/fields.c @@ -0,0 +1,380 @@ +/* $NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* Subroutines to generate sort keys. */ + +#include "sort.h" + +__RCSID("$NetBSD: fields.c,v 1.33 2013/01/20 10:12:58 apb Exp $"); + +#define SKIP_BLANKS(ptr) { \ + if (BLANK & d_mask[*(ptr)]) \ + while (BLANK & d_mask[*(++(ptr))]); \ +} + +#define NEXTCOL(pos) { \ + if (!SEP_FLAG) \ + while (BLANK & l_d_mask[*(++pos)]); \ + while ((*(pos+1) != '\0') && !((FLD_D | REC_D_F) & l_d_mask[*++pos]));\ +} + +static u_char *enterfield(u_char *, const u_char *, struct field *, int); +static u_char *number(u_char *, const u_char *, u_char *, u_char *, int); +static u_char *length(u_char *, const u_char *, u_char *, u_char *, int); + +#define DECIMAL_POINT '.' + +/* + * constructs sort key with leading recheader, followed by the key, + * followed by the original line. + */ +length_t +enterkey(RECHEADER *keybuf, const u_char *keybuf_end, u_char *line_data, + size_t line_size, struct field fieldtable[]) + /* keybuf: pointer to start of key */ +{ + int i; + u_char *l_d_mask; + u_char *lineend, *pos; + const u_char *endkey; + u_char *keypos; + struct coldesc *clpos; + int col = 1; + struct field *ftpos; + + l_d_mask = d_mask; + pos = line_data - 1; + lineend = line_data + line_size-1; + /* don't include rec_delimiter */ + + for (i = 0; i < ncols; i++) { + clpos = clist + i; + for (; (col < clpos->num) && (pos < lineend); col++) { + NEXTCOL(pos); + } + if (pos >= lineend) + break; + clpos->start = SEP_FLAG ? pos + 1 : pos; + NEXTCOL(pos); + clpos->end = pos; + col++; + if (pos >= lineend) { + clpos->end = lineend; + i++; + break; + } + } + for (; i <= ncols; i++) + clist[i].start = clist[i].end = lineend; + if (clist[0].start < line_data) + clist[0].start++; + + /* + * We write the sort keys (concatenated) followed by the + * original line data (for output) as the 'keybuf' data. + * keybuf->length is the number of key bytes + data bytes. + * keybuf->offset is the number of key bytes. + * We add a record separator weight after the key in case + * (as is usual) we need to preserve the order of equal lines, + * and for 'sort -u'. + * The key itself will have had the correct weight applied. + */ + keypos = keybuf->data; + endkey = keybuf_end - line_size - 1; + if (endkey <= keypos) + /* No room for any key bytes */ + return 1; + + for (ftpos = fieldtable + 1; ftpos->icol.num; ftpos++) { + if ((keypos = enterfield(keypos, endkey, ftpos, + fieldtable->flags)) == NULL) + return (1); + } + + keybuf->offset = keypos - keybuf->data; + keybuf->length = keybuf->offset + line_size; + + /* + * Posix requires that equal keys be further sorted by the + * entire original record. + * NetBSD has (at least for some time) kept equal keys in + * their original order. + * For 'sort -u' posix_sort is unset. + */ + keybuf->keylen = posix_sort ? keybuf->length : keybuf->offset; + + memcpy(keypos, line_data, line_size); + return (0); +} + +/* + * constructs a field (as defined by -k) within a key + */ +static u_char * +enterfield(u_char *tablepos, const u_char *endkey, struct field *cur_fld, + int gflags) +{ + u_char *start, *end, *lineend, *mask, *lweight; + struct column icol, tcol; + u_int flags; + + icol = cur_fld->icol; + tcol = cur_fld->tcol; + flags = cur_fld->flags; + start = icol.p->start; + lineend = clist[ncols].end; + if (flags & BI) + SKIP_BLANKS(start); + start += icol.indent; + start = min(start, lineend); + + if (!tcol.num) + end = lineend; + else { + if (tcol.indent) { + end = tcol.p->start; + if (flags & BT) + SKIP_BLANKS(end); + end += tcol.indent; + end = min(end, lineend); + } else + end = tcol.p->end; + } + + if (flags & L) + return length(tablepos, endkey, start, end, flags); + if (flags & N) + return number(tablepos, endkey, start, end, flags); + + /* Bound check space - assuming nothing is skipped */ + if (tablepos + (end - start) + 1 >= endkey) + return NULL; + + mask = cur_fld->mask; + lweight = cur_fld->weights; + for (; start < end; start++) { + if (!mask || mask[*start]) { + *tablepos++ = lweight[*start]; + } + } + /* Add extra byte (absent from lweight) to sort short keys correctly */ + *tablepos++ = lweight[REC_D]; + return tablepos; +} + +/* + * Numbers are converted to a floating point format (exponent & mantissa) + * so that they compare correctly as sequence of unsigned bytes. + * Bytes 0x00 and 0xff are used to terminate positive and negative numbers + * to ensure that 0.123 sorts after 0.12 and -0.123 sorts before -0.12. + * + * The first byte contain the overall sign, exponent sign and some of the + * exponent. These have to be ordered (-ve value, decreasing exponent), + * zero, (+ve value, increasing exponent). + * + * The first byte is 0x80 for zero, 0xc0 for +ve with exponent 0. + * -ve values are the 1's compliments (so 0x7f isn't used!). + * + * This only leaves 63 byte values for +ve exponents - which isn't enough. + * The largest 4 exponent values are used to hold a byte count of the + * number of following bytes that contain 8 exponent bits per byte, + * This lets us sort exponents from -2^31 to +2^31. + * + * The mantissa is stored 2 digits per byte offset by 0x40, for negative + * numbers the order must be reversed (they are bit inverted). + * + * Reverse sorts are done by inverting the sign of the number. + */ +#define MAX_EXP_ENC ((int)sizeof(int)) + +static u_char * +number(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend, + int reverse) +{ + int exponent = -1; + int had_dp = 0; + u_char *tline; + char ch; + unsigned int val; + u_char *last_nz_pos; + u_char negate; + + if (reverse & R) + negate = 0xff; + else + negate = 0; + + /* Give ourselves space for the key terminator */ + bufend--; + + /* Ensure we have enough space for the exponent */ + if (pos + 1 + MAX_EXP_ENC > bufend) + return (NULL); + + SKIP_BLANKS(line); + if (*line == '-') { /* set the sign */ + negate ^= 0xff; + line++; + } else if (*line == '+') { + line++; + } + + /* eat initial zeroes */ + for (; *line == '0' && line < lineend; line++) + continue; + + /* calculate exponents */ + if (*line == DECIMAL_POINT) { + /* Decimal fraction */ + had_dp = 1; + while (*++line == '0' && line < lineend) + exponent--; + } else { + /* Large (absolute) value, count digits */ + for (tline = line; *tline >= '0' && + *tline <= '9' && tline < lineend; tline++) + exponent++; + } + + /* If the first/next character isn't a digit, value is zero */ + if (*line < '1' || *line > '9' || line >= lineend) { + /* This may be "0", "0.00", "000" or "fubar" but sorts as 0 */ + /* XXX what about NaN, NAN, inf and INF */ + *pos++ = 0x80; + return pos; + } + + /* Maybe here we should allow for e+12 (etc) */ + + if (exponent < 0x40 - MAX_EXP_ENC && -exponent < 0x40 - MAX_EXP_ENC) { + /* Value ok for simple encoding */ + /* exponent 0 is 0xc0 for +ve numbers and 0x40 for -ve ones */ + exponent += 0xc0; + *pos++ = negate ^ exponent; + } else { + /* Out or range for a single byte */ + int c, t; + t = exponent > 0 ? exponent : -exponent; + /* Count how many 8-bit bytes are needed */ + for (c = 0; ; c++) { + t >>= 8; + if (t == 0) + break; + } + /* 'c' better be 0..3 here - but probably 0..1 */ + /* Offset just outside valid range */ + t = c + 0x40 - MAX_EXP_ENC; + if (exponent < 0) + t = -t; + *pos++ = negate ^ (t + 0xc0); + /* now add each byte, most significant first */ + for (; c >= 0; c--) + *pos++ = negate ^ (exponent >> (c * 8)); + } + + /* Finally add mantissa, 2 digits per byte */ + for (last_nz_pos = pos; line < lineend; ) { + if (pos >= bufend) + return NULL; + ch = *line++; + val = (ch - '0') * 10; + if (val > 90) { + if (ch == DECIMAL_POINT && !had_dp) { + had_dp = 1; + continue; + } + break; + } + while (line < lineend) { + ch = *line++; + if (ch == DECIMAL_POINT && !had_dp) { + had_dp = 1; + continue; + } + if (ch < '0' || ch > '9') + line = lineend; + else + val += ch - '0'; + break; + } + *pos++ = negate ^ (val + 0x40); + if (val != 0) + last_nz_pos = pos; + } + + /* Add key terminator, deleting any trailing "00" */ + *last_nz_pos++ = negate; + + return (last_nz_pos); +} + +static u_char * +length(u_char *pos, const u_char *bufend, u_char *line, u_char *lineend, + int flag) +{ + u_char buf[32]; + int l; + SKIP_BLANKS(line); + l = snprintf((char *)buf, sizeof(buf), "%td", lineend - line); + return number(pos, bufend, buf, buf + l, flag); +} diff --git a/usr.bin/sort/files.c b/usr.bin/sort/files.c new file mode 100644 index 0000000..7cb27c5 --- /dev/null +++ b/usr.bin/sort/files.c @@ -0,0 +1,279 @@ +/* $NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sort.h" +#include "fsort.h" + +__RCSID("$NetBSD: files.c,v 1.42 2015/08/05 07:10:03 mrg Exp $"); + +#include + +/* Align records in temporary files to avoid misaligned copies */ +#define REC_ROUNDUP(n) (((n) + sizeof (long) - 1) & ~(sizeof (long) - 1)) + +static ssize_t seq(FILE *, u_char **); + +/* + * this is called when there is no special key. It's only called + * in the first fsort pass. + */ + +static u_char *opos; +static size_t osz; + +void +makeline_copydown(RECHEADER *recbuf) +{ + memmove(recbuf->data, opos, osz); +} + +int +makeline(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *dummy2) +{ + u_char *pos; + int c; + + pos = recbuf->data; + if (osz != 0) { + /* + * Buffer shortage is solved by either of two ways: + * o flush previous buffered data and start using the + * buffer from start. + * makeline_copydown() above must be called. + * o realloc buffer + * + * This code has relied on realloc changing 'bufend', + * but that isn't necessarily true. + */ + pos += osz; + osz = 0; + } + + while (pos < bufend) { + c = getc(fp); + if (c == EOF) { + if (pos == recbuf->data) { + FCLOSE(fp); + return EOF; + } + /* Add terminator to partial line */ + c = REC_D; + } + *pos++ = c; + if (c == REC_D) { + recbuf->offset = 0; + recbuf->length = pos - recbuf->data; + recbuf->keylen = recbuf->length - 1; + return (0); + } + } + + /* Ran out of buffer space... */ + if (recbuf->data < bufend) { + /* Remember where the partial record is */ + osz = pos - recbuf->data; + opos = recbuf->data; + } + return (BUFFEND); +} + +/* + * This generates keys. It's only called in the first fsort pass + */ +int +makekey(FILE *fp, RECHEADER *recbuf, u_char *bufend, struct field *ftbl) +{ + static u_char *line_data; + static ssize_t line_size; + static int overflow = 0; + + /* We get re-entered after returning BUFFEND - save old data */ + if (overflow) { + overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl); + return overflow ? BUFFEND : 0; + } + + line_size = seq(fp, &line_data); + if (line_size == 0) { + FCLOSE(fp); + return EOF; + } + + if (line_size > bufend - recbuf->data) { + overflow = 1; + } else { + overflow = enterkey(recbuf, bufend, line_data, line_size, ftbl); + } + return overflow ? BUFFEND : 0; +} + +/* + * get a line of input from fp + */ +static ssize_t +seq(FILE *fp, u_char **line) +{ + static u_char *buf; + static size_t buf_size = DEFLLEN; + u_char *end, *pos; + int c; + u_char *new_buf; + + if (!buf) { + /* one-time initialization */ + buf = malloc(buf_size); + if (!buf) + err(2, "malloc of linebuf for %zu bytes failed", + buf_size); + } + + end = buf + buf_size; + pos = buf; + while ((c = getc(fp)) != EOF) { + *pos++ = c; + if (c == REC_D) { + *line = buf; + return pos - buf; + } + if (pos == end) { + /* Long line - double size of buffer */ + /* XXX: Check here for stupidly long lines */ + buf_size *= 2; + new_buf = realloc(buf, buf_size); + if (!new_buf) + err(2, "realloc of linebuf to %zu bytes failed", + buf_size); + + end = new_buf + buf_size; + pos = new_buf + (pos - buf); + buf = new_buf; + } + } + + if (pos != buf) { + /* EOF part way through line - add line terminator */ + *pos++ = REC_D; + *line = buf; + return pos - buf; + } + + return 0; +} + +/* + * write a key/line pair to a temporary file + */ +void +putrec(const RECHEADER *rec, FILE *fp) +{ + EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length), fp, + "failed to write temp file"); +} + +/* + * write a line to output + */ +void +putline(const RECHEADER *rec, FILE *fp) +{ + EWRITE(rec->data+rec->offset, 1, rec->length - rec->offset, fp, + "failed to write"); +} + +/* + * write dump of key to output (for -Dk) + */ +void +putkeydump(const RECHEADER *rec, FILE *fp) +{ + EWRITE(rec, 1, REC_ROUNDUP(offsetof(RECHEADER, data) + rec->offset), fp, + "failed to write debug key"); +} + +/* + * get a record from a temporary file. (Used by merge sort.) + */ +int +geteasy(FILE *fp, RECHEADER *rec, u_char *end, struct field *dummy2) +{ + length_t file_len; + int i; + + (void)sizeof (char[offsetof(RECHEADER, length) == 0 ? 1 : -1]); + + if ((u_char *)(rec + 1) > end) + return (BUFFEND); + if (!fread(&rec->length, 1, sizeof rec->length, fp)) { + fclose(fp); + return (EOF); + } + file_len = REC_ROUNDUP(offsetof(RECHEADER, data) + rec->length); + if (end - rec->data < (ptrdiff_t)file_len) { + for (i = sizeof rec->length - 1; i >= 0; i--) + ungetc(*((char *) rec + i), fp); + return (BUFFEND); + } + + fread(&rec->length + 1, file_len - sizeof rec->length, 1, fp); + return (0); +} diff --git a/usr.bin/sort/fsort.c b/usr.bin/sort/fsort.c new file mode 100644 index 0000000..c229ae2 --- /dev/null +++ b/usr.bin/sort/fsort.c @@ -0,0 +1,202 @@ +/* $NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Read in a block of records (until 'enough'). + * sort, write to temp file. + * Merge sort temp files into output file + * Small files miss out the temp file stage. + * Large files might get multiple merges. + */ +#include "sort.h" +#include "fsort.h" + +__RCSID("$NetBSD: fsort.c,v 1.47 2010/02/05 21:58:41 enami Exp $"); + +#include +#include + +#define SALIGN(n) ((n+sizeof(length_t)-1) & ~(sizeof(length_t)-1)) + +void +fsort(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl) +{ + RECHEADER **keylist; + RECHEADER **keypos, **keyp; + RECHEADER *buffer; + size_t bufsize = DEFBUFSIZE; + u_char *bufend; + int mfct = 0; + int c, nelem; + get_func_t get; + RECHEADER *crec; + RECHEADER *nbuffer; + FILE *fp, *tmp_fp; + int file_no; + int max_recs = DEBUG('m') ? 16 : MAXNUM; + + buffer = allocrec(NULL, bufsize); + bufend = (u_char *)buffer + bufsize; + /* Allocate double length keymap for radix_sort */ + keylist = malloc(2 * max_recs * sizeof(*keylist)); + if (buffer == NULL || keylist == NULL) + err(2, "failed to malloc initial buffer or keylist"); + + if (SINGL_FLD) + /* Key and data are one! */ + get = makeline; + else + /* Key (merged key fields) added before data */ + get = makekey; + + file_no = 0; + fp = fopen(filelist->names[0], "r"); + if (fp == NULL) + err(2, "%s", filelist->names[0]); + + /* Loop through reads of chunk of input files that get sorted + * and then merged together. */ + for (;;) { + keypos = keylist; + nelem = 0; + crec = buffer; + makeline_copydown(crec); + + /* Loop reading records */ + for (;;) { + c = get(fp, crec, bufend, ftbl); + /* 'c' is 0, EOF or BUFFEND */ + if (c == 0) { + /* Save start of key in input buffer */ + *keypos++ = crec; + if (++nelem == max_recs) { + c = BUFFEND; + break; + } + crec = (RECHEADER *)(crec->data + SALIGN(crec->length)); + continue; + } + if (c == EOF) { + /* try next file */ + if (++file_no >= nfiles) + /* no more files */ + break; + fp = fopen(filelist->names[file_no], "r"); + if (fp == NULL) + err(2, "%s", filelist->names[file_no]); + continue; + } + if (nelem >= max_recs + || (bufsize >= MAXBUFSIZE && nelem > 8)) + /* Need to sort and save this lot of data */ + break; + + /* c == BUFFEND, and we can process more data */ + /* Allocate a larger buffer for this lot of data */ + bufsize *= 2; + nbuffer = allocrec(buffer, bufsize); + if (!nbuffer) { + err(2, "failed to realloc buffer to %zu bytes", + bufsize); + } + + /* patch up keylist[] */ + for (keyp = &keypos[-1]; keyp >= keylist; keyp--) + *keyp = nbuffer + (*keyp - buffer); + + crec = nbuffer + (crec - buffer); + buffer = nbuffer; + bufend = (u_char *)buffer + bufsize; + } + + /* Sort this set of records */ + radix_sort(keylist, keylist + max_recs, nelem); + + if (c == EOF && mfct == 0) { + /* all the data is (sorted) in the buffer */ + append(keylist, nelem, outfp, + DEBUG('k') ? putkeydump : putline); + break; + } + + /* Save current data to a temporary file for a later merge */ + if (nelem != 0) { + tmp_fp = ftmp(); + append(keylist, nelem, tmp_fp, putrec); + save_for_merge(tmp_fp, geteasy, ftbl); + } + mfct = 1; + + if (c == EOF) { + /* merge to output file */ + merge_sort(outfp, + DEBUG('k') ? putkeydump : putline, ftbl); + break; + } + } + + free(keylist); + keylist = NULL; + free(buffer); + buffer = NULL; +} diff --git a/usr.bin/sort/fsort.h b/usr.bin/sort/fsort.h new file mode 100644 index 0000000..3d8ef4c --- /dev/null +++ b/usr.bin/sort/fsort.h @@ -0,0 +1,78 @@ +/* $NetBSD: fsort.h,v 1.17 2009/09/26 21:16:55 dsl Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)fsort.h 8.1 (Berkeley) 6/6/93 + */ + +#define BUFSIZE (1<<20) +#define MAXNUM 131072 /* low guess at average record count */ +#define BUFFEND (EOF-2) +#define MAXFCT 1000 +#define DEFLLEN 65536 + +/* + * Default (initial) and maximum size of record buffer for fsort(). + * Note that no more than MAXNUM records are stored in the buffer, + * even if the buffer is not full yet. + */ +#define DEFBUFSIZE (1 << 20) /* 1MB */ +#define MAXBUFSIZE (8 << 20) /* 10 MB */ diff --git a/usr.bin/sort/init.c b/usr.bin/sort/init.c new file mode 100644 index 0000000..ff1799b --- /dev/null +++ b/usr.bin/sort/init.c @@ -0,0 +1,447 @@ +/* $NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sort.h" + +__RCSID("$NetBSD: init.c,v 1.29 2013/10/18 20:47:06 christos Exp $"); + +#include +#include + +static void insertcol(struct field *); +static const char *setcolumn(const char *, struct field *); + +/* + * masks of ignored characters. + */ +static u_char dtable[NBINS], itable[NBINS]; + +/* + * parsed key options + */ +struct coldesc *clist = NULL; +int ncols = 0; + +/* + * clist (list of columns which correspond to one or more icol or tcol) + * is in increasing order of columns. + * Fields are kept in increasing order of fields. + */ + +/* + * keep clist in order--inserts a column in a sorted array + */ +static void +insertcol(struct field *field) +{ + int i; + struct coldesc *p; + + /* Make space for new item */ + p = realloc(clist, (ncols + 2) * sizeof(*clist)); + if (!p) + err(1, "realloc"); + clist = p; + memset(&clist[ncols], 0, sizeof(clist[ncols])); + + for (i = 0; i < ncols; i++) + if (field->icol.num <= clist[i].num) + break; + if (field->icol.num != clist[i].num) { + memmove(clist+i+1, clist+i, sizeof(COLDESC)*(ncols-i)); + clist[i].num = field->icol.num; + ncols++; + } + if (field->tcol.num && field->tcol.num != field->icol.num) { + for (i = 0; i < ncols; i++) + if (field->tcol.num <= clist[i].num) + break; + if (field->tcol.num != clist[i].num) { + memmove(clist+i+1, clist+i,sizeof(COLDESC)*(ncols-i)); + clist[i].num = field->tcol.num; + ncols++; + } + } +} + +/* + * matches fields with the appropriate columns--n^2 but who cares? + */ +void +fldreset(struct field *fldtab) +{ + int i; + + fldtab[0].tcol.p = clist + ncols - 1; + for (++fldtab; fldtab->icol.num; ++fldtab) { + for (i = 0; fldtab->icol.num != clist[i].num; i++) + ; + fldtab->icol.p = clist + i; + if (!fldtab->tcol.num) + continue; + for (i = 0; fldtab->tcol.num != clist[i].num; i++) + ; + fldtab->tcol.p = clist + i; + } +} + +/* + * interprets a column in a -k field + */ +static const char * +setcolumn(const char *pos, struct field *cur_fld) +{ + struct column *col; + char *npos; + int tmp; + col = cur_fld->icol.num ? (&cur_fld->tcol) : (&cur_fld->icol); + col->num = (int) strtol(pos, &npos, 10); + pos = npos; + if (col->num <= 0 && !(col->num == 0 && col == &(cur_fld->tcol))) + errx(2, "field numbers must be positive"); + if (*pos == '.') { + if (!col->num) + errx(2, "cannot indent end of line"); + ++pos; + col->indent = (int) strtol(pos, &npos, 10); + pos = npos; + if (&cur_fld->icol == col) + col->indent--; + if (col->indent < 0) + errx(2, "illegal offset"); + } + for(; (tmp = optval(*pos, cur_fld->tcol.num)); pos++) + cur_fld->flags |= tmp; + if (cur_fld->icol.num == 0) + cur_fld->icol.num = 1; + return (pos); +} + +int +setfield(const char *pos, struct field *cur_fld, int gflag) +{ + cur_fld->mask = NULL; + + pos = setcolumn(pos, cur_fld); + if (*pos == '\0') /* key extends to EOL. */ + cur_fld->tcol.num = 0; + else { + if (*pos != ',') + errx(2, "illegal field descriptor"); + setcolumn((++pos), cur_fld); + } + if (!cur_fld->flags) + cur_fld->flags = gflag; + if (REVERSE) + /* A local 'r' doesn't invert the global one */ + cur_fld->flags &= ~R; + + /* Assign appropriate mask table and weight table. */ + cur_fld->weights = weight_tables[cur_fld->flags & (R | F)]; + if (cur_fld->flags & I) + cur_fld->mask = itable; + else if (cur_fld->flags & D) + cur_fld->mask = dtable; + + cur_fld->flags |= (gflag & (BI | BT)); + if (!cur_fld->tcol.indent) /* BT has no meaning at end of field */ + cur_fld->flags &= ~BT; + + if (cur_fld->tcol.num + && !(!(cur_fld->flags & BI) && cur_fld->flags & BT) + && (cur_fld->tcol.num <= cur_fld->icol.num + /* indent if 0 -> end of field, i.e. okay */ + && cur_fld->tcol.indent != 0 + && cur_fld->tcol.indent < cur_fld->icol.indent)) + errx(2, "fields out of order"); + + insertcol(cur_fld); + return (cur_fld->tcol.num); +} + +int +optval(int desc, int tcolflag) +{ + switch(desc) { + case 'b': + if (!tcolflag) + return BI; + else + return BT; + case 'd': return D; + case 'f': return F; + case 'i': return I; + case 'l': return L; + case 'n': return N; + case 'r': return R; + default: return 0; + } +} + +/* + * Return true if the options found in ARG, according to the getopt + * spec in OPTS, require an additional argv word as an option + * argument. + */ +static int +options_need_argument(const char *arg, const char *opts) +{ + size_t pos; + const char *s; + + /*assert(arg[0] == '-');*/ + + pos = 1; + while (arg[pos]) { + s = strchr(opts, arg[pos]); + if (s == NULL) { + /* invalid option */ + return 0; + } + if (s[1] == ':') { + /* option requires argument */ + if (arg[pos+1] == '\0') { + /* no argument in this arg */ + return 1; + } + else { + /* argument is in this arg; no more options */ + return 0; + } + } + pos++; + } + return 0; +} + +/* + * Replace historic +SPEC arguments with appropriate -kSPEC. + * + * The form can be either a single +SPEC or a pair +SPEC -SPEC. + * The following -SPEC is not recognized unless it follows + * immediately. + */ +void +fixit(int *argc, char **argv, const char *opts) +{ + int i, j, sawplus; + char *vpos, *tpos, spec[20]; + int col, indent; + + sawplus = 0; + for (i = 1; i < *argc; i++) { + /* + * This loop must stop exactly where getopt will stop. + * Otherwise it turns e.g. "sort x +3" into "sort x + * -k4.1", which will croak if +3 was in fact really a + * file name. In order to do this reliably we need to + * be able to identify argv words that are option + * arguments. + */ + + if (!strcmp(argv[i], "--")) { + /* End of options; stop. */ + break; + } + + if (argv[i][0] == '+') { + /* +POS argument */ + sawplus = 1; + } else if (argv[i][0] == '-' && sawplus && + isdigit((unsigned char)argv[i][1])) { + /* -POS argument */ + sawplus = 0; + } else if (argv[i][0] == '-') { + /* other option */ + sawplus = 0; + if (options_need_argument(argv[i], opts)) { + /* skip over the argument */ + i++; + } + continue; + } else { + /* not an option at all; stop */ + sawplus = 0; + break; + } + + /* + * At this point argv[i] is an old-style spec. The + * sawplus flag used by the above loop logic also + * tells us if it's a +SPEC or -SPEC. + */ + + /* parse spec */ + tpos = argv[i]+1; + col = (int)strtol(tpos, &tpos, 10); + if (*tpos == '.') { + ++tpos; + indent = (int) strtol(tpos, &tpos, 10); + } else + indent = 0; + /* tpos now points to the optional flags */ + + /* + * In the traditional form, x.0 means beginning of line; + * in the new form, x.0 means end of line. Adjust the + * value of INDENT accordingly. + */ + if (sawplus) { + /* +POS */ + col += 1; + indent += 1; + } else { + /* -POS */ + if (indent > 0) + col += 1; + } + + /* make the new style spec */ + (void)snprintf(spec, sizeof(spec), "%d.%d%s", col, indent, + tpos); + + if (sawplus) { + /* Replace the +POS argument with new-style -kSPEC */ + asprintf(&vpos, "-k%s", spec); + argv[i] = vpos; + } else { + /* + * Append the spec to the one from the + * preceding +POS argument, and remove the + * current argv element entirely. + */ + asprintf(&vpos, "%s,%s", argv[i-1], spec); + free(argv[i-1]); + argv[i-1] = vpos; + for (j=i; j < *argc; j++) + argv[j] = argv[j+1]; + *argc -= 1; + i--; + } + } +} + +/* + * ascii, Rascii, Ftable, and RFtable map + * + * Sorting 'weight' tables. + * Convert 'ascii' characters into their sort order. + * The 'F' variants fold lower case to upper equivalent + * The 'R' variants are for reverse sorting. + * + * The record separator (REC_D) never needs a weight, this frees one + * byte value as an 'end of key' marker. This must be 0 for normal + * weight tables, and 0xff for reverse weight tables - and is used + * to terminate keys so that short keys sort before (after if reverse) + * longer keys. + * + * The field separator has a normal weight - although it cannot occur + * within a key unless it is the default (space+tab). + * + * All other bytes map to the appropriate value for the sort order. + * Numeric sorts don't need any tables, they are reversed by negation. + * + * Global reverse sorts are done by writing the sorted keys in reverse + * order - the sort itself is stil forwards. + * This means that weights are only ever used when generating keys, any + * sort of the original data bytes is always forwards and unweighted. + * + * Note: this is only good for ASCII sorting. For different LC 's, + * all bets are off. + * + * itable[] and dtable[] are the masks for -i (ignore non-printables) + * and -d (only sort blank and alphanumerics). + */ +void +settables(void) +{ + int i; + int next_weight = 1; + int rev_weight = 254; + + ascii[REC_D] = 0; + Rascii[REC_D] = 255; + Ftable[REC_D] = 0; + RFtable[REC_D] = 255; + + for (i = 0; i < 256; i++) { + if (i == REC_D) + continue; + ascii[i] = next_weight; + Rascii[i] = rev_weight; + if (Ftable[i] == 0) { + Ftable[i] = next_weight; + RFtable[i] = rev_weight; + Ftable[tolower(i)] = next_weight; + RFtable[tolower(i)] = rev_weight; + } + next_weight++; + rev_weight--; + + if (i == '\n' || isprint(i)) + itable[i] = 1; + + if (i == '\n' || i == '\t' || i == ' ' || isalnum(i)) + dtable[i] = 1; + } +} diff --git a/usr.bin/sort/msort.c b/usr.bin/sort/msort.c new file mode 100644 index 0000000..6cbcf90 --- /dev/null +++ b/usr.bin/sort/msort.c @@ -0,0 +1,431 @@ +/* $NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "sort.h" +#include "fsort.h" + +__RCSID("$NetBSD: msort.c,v 1.31 2016/06/01 02:37:55 kre Exp $"); + +#include +#include +#include +#include + +/* Subroutines using comparisons: merge sort and check order */ +#define DELETE (1) + +typedef struct mfile { + FILE *fp; + get_func_t get; + RECHEADER *rec; + u_char *end; +} MFILE; + +static int cmp(RECHEADER *, RECHEADER *); +static int insert(struct mfile **, struct mfile *, int, int); +static void merge_sort_fstack(FILE *, put_func_t, struct field *); + +/* + * Number of files merge() can merge in one pass. + */ +#define MERGE_FNUM 16 + +static struct mfile fstack[MERGE_FNUM]; +static struct mfile fstack_1[MERGE_FNUM]; +static struct mfile fstack_2[MERGE_FNUM]; +static int fstack_count, fstack_1_count, fstack_2_count; + +void +save_for_merge(FILE *fp, get_func_t get, struct field *ftbl) +{ + FILE *mfp, *mfp1, *mfp2; + + if (fstack_count == MERGE_FNUM) { + /* Must reduce the number of temporary files */ + mfp = ftmp(); + merge_sort_fstack(mfp, putrec, ftbl); + /* Save output in next layer */ + if (fstack_1_count == MERGE_FNUM) { + mfp1 = ftmp(); + memcpy(fstack, fstack_1, sizeof fstack); + merge_sort_fstack(mfp1, putrec, ftbl); + if (fstack_2_count == MERGE_FNUM) { + /* More than 4096 files! */ + mfp2 = ftmp(); + memcpy(fstack, fstack_2, sizeof fstack); + merge_sort_fstack(mfp2, putrec, ftbl); + fstack_2[0].fp = mfp2; + fstack_2_count = 1; + } + fstack_2[fstack_2_count].fp = mfp1; + fstack_2[fstack_2_count].get = geteasy; + fstack_2_count++; + fstack_1_count = 0; + } + fstack_1[fstack_1_count].fp = mfp; + fstack_1[fstack_1_count].get = geteasy; + fstack_1_count++; + fstack_count = 0; + } + + fstack[fstack_count].fp = fp; + fstack[fstack_count++].get = get; +} + +void +fmerge(struct filelist *filelist, int nfiles, FILE *outfp, struct field *ftbl) +{ + get_func_t get = SINGL_FLD ? makeline : makekey; + FILE *fp; + int i; + + for (i = 0; i < nfiles; i++) { + fp = fopen(filelist->names[i], "r"); + if (fp == NULL) + err(2, "%s", filelist->names[i]); + save_for_merge(fp, get, ftbl); + } + + merge_sort(outfp, putline, ftbl); +} + +void +merge_sort(FILE *outfp, put_func_t put, struct field *ftbl) +{ + int count = fstack_1_count + fstack_2_count; + FILE *mfp; + int i; + + if (count == 0) { + /* All files in initial array */ + merge_sort_fstack(outfp, put, ftbl); + return; + } + + count += fstack_count; + + /* Too many files for one merge sort */ + for (;;) { + /* Sort latest 16 files */ + i = count; + if (i > MERGE_FNUM) + i = MERGE_FNUM; + while (fstack_count > 0) + fstack[--i] = fstack[--fstack_count]; + while (i > 0 && fstack_1_count > 0) + fstack[--i] = fstack_1[--fstack_1_count]; + while (i > 0) + fstack[--i] = fstack_2[--fstack_2_count]; + if (count <= MERGE_FNUM) { + /* Got all the data */ + fstack_count = count; + merge_sort_fstack(outfp, put, ftbl); + return; + } + mfp = ftmp(); + fstack_count = count > MERGE_FNUM ? MERGE_FNUM : count; + merge_sort_fstack(mfp, putrec, ftbl); + fstack[0].fp = mfp; + fstack[0].get = geteasy; + fstack_count = 1; + count -= MERGE_FNUM - 1; + } +} + +static void +merge_sort_fstack(FILE *outfp, put_func_t put, struct field *ftbl) +{ + struct mfile *flistb[MERGE_FNUM], **flist = flistb, *cfile; + RECHEADER *new_rec; + u_char *new_end; + void *tmp; + int c, i, nfiles; + size_t sz; + + /* Read one record from each file (read again if a duplicate) */ + for (nfiles = i = 0; i < fstack_count; i++) { + cfile = &fstack[i]; + if (cfile->rec == NULL) { + cfile->rec = allocrec(NULL, DEFLLEN); + cfile->end = (u_char *)cfile->rec + DEFLLEN; + } + rewind(cfile->fp); + + for (;;) { + c = cfile->get(cfile->fp, cfile->rec, cfile->end, ftbl); + if (c == EOF) + break; + + if (c == BUFFEND) { + /* Double buffer size */ + sz = (cfile->end - (u_char *)cfile->rec) * 2; + cfile->rec = allocrec(cfile->rec, sz); + cfile->end = (u_char *)cfile->rec + sz; + continue; + } + + if (nfiles != 0) { + if (insert(flist, cfile, nfiles, !DELETE)) + /* Duplicate removed */ + continue; + } else + flist[0] = cfile; + nfiles++; + break; + } + } + + if (nfiles == 0) + return; + + /* + * We now loop reading a new record from the file with the + * 'sorted first' existing record. + * As each record is added, the 'first' record is written to the + * output file - maintaining one record from each file in the sorted + * list. + */ + new_rec = allocrec(NULL, DEFLLEN); + new_end = (u_char *)new_rec + DEFLLEN; + for (;;) { + cfile = flist[0]; + c = cfile->get(cfile->fp, new_rec, new_end, ftbl); + if (c == EOF) { + /* Write out last record from now-empty input */ + put(cfile->rec, outfp); + if (--nfiles == 0) + break; + /* Replace from file with now-first sorted record. */ + /* (Moving base 'flist' saves copying everything!) */ + flist++; + continue; + } + if (c == BUFFEND) { + /* Buffer not large enough - double in size */ + sz = (new_end - (u_char *)new_rec) * 2; + new_rec = allocrec(new_rec, sz); + new_end = (u_char *)new_rec +sz; + continue; + } + + /* Swap in new buffer, saving old */ + tmp = cfile->rec; + cfile->rec = new_rec; + new_rec = tmp; + tmp = cfile->end; + cfile->end = new_end; + new_end = tmp; + + /* Add into sort, removing the original first entry */ + c = insert(flist, cfile, nfiles, DELETE); + if (c != 0 || (UNIQUE && cfile == flist[0] + && cmp(new_rec, cfile->rec) == 0)) { + /* Was an unwanted duplicate, restore buffer */ + tmp = cfile->rec; + cfile->rec = new_rec; + new_rec = tmp; + tmp = cfile->end; + cfile->end = new_end; + new_end = tmp; + continue; + } + + /* Write out 'old' record */ + put(new_rec, outfp); + } + + free(new_rec); +} + +/* + * if delete: inserts rec in flist, deletes flist[0]; + * otherwise just inserts *rec in flist. + * Returns 1 if record is a duplicate to be ignored. + */ +static int +insert(struct mfile **flist, struct mfile *rec, int ttop, int delete) +{ + int mid, top = ttop, bot = 0, cmpv = 1; + + for (mid = top / 2; bot + 1 != top; mid = (bot + top) / 2) { + cmpv = cmp(rec->rec, flist[mid]->rec); + if (cmpv == 0 ) { + if (UNIQUE) + /* Duplicate key, read another record */ + /* NB: This doesn't guarantee to keep any + * particular record. */ + return 1; + /* + * Apply sort by input file order. + * We could truncate the sort is the fileno are + * adjacent - but that is all too hard! + * The fileno cannot be equal, since we only have one + * record from each file (+ flist[0] which never + * comes here). + */ + cmpv = rec < flist[mid] ? -1 : 1; + if (REVERSE) + cmpv = -cmpv; + } + if (cmpv < 0) + top = mid; + else + bot = mid; + } + + /* At this point we haven't yet compared against flist[0] */ + + if (delete) { + /* flist[0] is ourselves, only the caller knows the old data */ + if (bot != 0) { + memmove(flist, flist + 1, bot * sizeof(MFILE *)); + flist[bot] = rec; + } + return 0; + } + + /* Inserting original set of records */ + + if (bot == 0 && cmpv != 0) { + /* Doesn't match flist[1], must compare with flist[0] */ + cmpv = cmp(rec->rec, flist[0]->rec); + if (cmpv == 0 && UNIQUE) + return 1; + /* Add matching keys in file order (ie new is later) */ + if (cmpv < 0) + bot = -1; + } + bot++; + memmove(flist + bot + 1, flist + bot, (ttop - bot) * sizeof(MFILE *)); + flist[bot] = rec; + return 0; +} + +/* + * check order on one file + */ +void +order(struct filelist *filelist, struct field *ftbl, int quiet) +{ + get_func_t get = SINGL_FLD ? makeline : makekey; + RECHEADER *crec, *prec, *trec; + u_char *crec_end, *prec_end, *trec_end; + FILE *fp; + int c; + + fp = fopen(filelist->names[0], "r"); + if (fp == NULL) + err(2, "%s", filelist->names[0]); + + crec = malloc(offsetof(RECHEADER, data[DEFLLEN])); + crec_end = crec->data + DEFLLEN; + prec = malloc(offsetof(RECHEADER, data[DEFLLEN])); + prec_end = prec->data + DEFLLEN; + + /* XXX this does exit(0) for overlong lines */ + if (get(fp, prec, prec_end, ftbl) != 0) + exit(0); + while (get(fp, crec, crec_end, ftbl) == 0) { + if (0 < (c = cmp(prec, crec))) { + if (quiet) + exit(1); + crec->data[crec->length-1] = 0; + errx(1, "found disorder: %s", crec->data+crec->offset); + } + if (UNIQUE && !c) { + if (quiet) + exit(1); + crec->data[crec->length-1] = 0; + errx(1, "found non-uniqueness: %s", + crec->data+crec->offset); + } + /* + * Swap pointers so that this record is on place pointed + * to by prec and new record is read to place pointed to by + * crec. + */ + trec = prec; + prec = crec; + crec = trec; + trec_end = prec_end; + prec_end = crec_end; + crec_end = trec_end; + } + exit(0); +} + +static int +cmp(RECHEADER *rec1, RECHEADER *rec2) +{ + int len; + int r; + + /* key is weights */ + len = min(rec1->keylen, rec2->keylen); + r = memcmp(rec1->data, rec2->data, len); + if (r == 0) + r = rec1->keylen - rec2->keylen; + if (REVERSE) + r = -r; + return r; +} diff --git a/usr.bin/sort/pathnames.h b/usr.bin/sort/pathnames.h new file mode 100644 index 0000000..534671e --- /dev/null +++ b/usr.bin/sort/pathnames.h @@ -0,0 +1,66 @@ +/* $NetBSD: pathnames.h,v 1.6 2008/04/28 20:24:15 martin Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +#define _PATH_STDIN "/dev/stdin" diff --git a/usr.bin/sort/radix_sort.c b/usr.bin/sort/radix_sort.c new file mode 100644 index 0000000..4ec5ff8 --- /dev/null +++ b/usr.bin/sort/radix_sort.c @@ -0,0 +1,217 @@ +/* $NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy and by Dan Bernstein at New York University, + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#if defined(LIBC_SCCS) && !defined(lint) +#if 0 +static char sccsid[] = "@(#)radixsort.c 8.2 (Berkeley) 4/28/95"; +#else +__RCSID("$NetBSD: radix_sort.c,v 1.4 2009/09/19 16:18:00 dsl Exp $"); +#endif +#endif /* LIBC_SCCS and not lint */ + +/* + * 'stable' radix sort initially from libc/stdlib/radixsort.c + */ + +#include + +#include +#include +#include +#include "sort.h" + +typedef struct { + RECHEADER **sa; /* Base of saved area */ + int sn; /* Number of entries */ + int si; /* index into data for compare */ +} stack; + +static void simplesort(RECHEADER **, int, int); + +#define THRESHOLD 20 /* Divert to simplesort(). */ + +#define empty(s) (s >= sp) +#define pop(a, n, i) a = (--sp)->sa, n = sp->sn, i = sp->si +#define push(a, n, i) sp->sa = a, sp->sn = n, (sp++)->si = i +#define swap(a, b, t) t = a, a = b, b = t + +void +radix_sort(RECHEADER **a, RECHEADER **ta, int n) +{ + u_int count[256], nc, bmin; + u_int c; + RECHEADER **ak, **tai, **lim; + RECHEADER *hdr; + int stack_size = 512; + stack *s, *sp, *sp0, *sp1, temp; + RECHEADER **top[256]; + u_int *cp, bigc; + int data_index = 0; + + if (n < THRESHOLD && !DEBUG('r')) { + simplesort(a, n, 0); + return; + } + + s = emalloc(stack_size * sizeof *s); + memset(&count, 0, sizeof count); + /* Technically 'top' doesn't need zeroing */ + memset(&top, 0, sizeof top); + + sp = s; + push(a, n, data_index); + while (!empty(s)) { + pop(a, n, data_index); + if (n < THRESHOLD && !DEBUG('r')) { + simplesort(a, n, data_index); + continue; + } + + /* Count number of times each 'next byte' occurs */ + nc = 0; + bmin = 255; + lim = a + n; + for (ak = a, tai = ta; ak < lim; ak++) { + hdr = *ak; + if (data_index >= hdr->keylen) { + /* Short key, copy to start of output */ + if (UNIQUE && a != sp->sa) + /* Stop duplicate being written out */ + hdr->keylen = -1; + *a++ = hdr; + n--; + continue; + } + /* Save in temp buffer for distribute */ + *tai++ = hdr; + c = hdr->data[data_index]; + if (++count[c] == 1) { + if (c < bmin) + bmin = c; + nc++; + } + } + /* + * We need save the bounds for each 'next byte' that + * occurs more so we can sort each block. + */ + if (sp + nc > s + stack_size) { + stack_size *= 2; + sp1 = erealloc(s, stack_size * sizeof *s); + sp = sp1 + (sp - s); + s = sp1; + } + + /* Minor optimisation to do the largest set last */ + sp0 = sp1 = sp; + bigc = 2; + /* Convert 'counts' positions, saving bounds for later sorts */ + ak = a; + for (cp = count + bmin; nc > 0; cp++) { + while (*cp == 0) + cp++; + if ((c = *cp) > 1) { + if (c > bigc) { + bigc = c; + sp1 = sp; + } + push(ak, c, data_index+1); + } + ak += c; + top[cp-count] = ak; + *cp = 0; /* Reset count[]. */ + nc--; + } + swap(*sp0, *sp1, temp); + + for (ak = ta+n; --ak >= ta;) /* Deal to piles. */ + *--top[(*ak)->data[data_index]] = *ak; + } + + free(s); +} + +/* insertion sort, short records are sorted before long ones */ +static void +simplesort(RECHEADER **a, int n, int data_index) +{ + RECHEADER **ak, **ai; + RECHEADER *akh; + RECHEADER **lim = a + n; + const u_char *s, *t; + int s_len, t_len; + int i; + int r; + + if (n <= 1) + return; + + for (ak = a+1; ak < lim; ak++) { + akh = *ak; + s = akh->data; + s_len = akh->keylen; + for (ai = ak; ;) { + ai--; + t_len = (*ai)->keylen; + if (t_len != -1) { + t = (*ai)->data; + for (i = data_index; ; i++) { + if (i >= s_len || i >= t_len) { + r = s_len - t_len; + break; + } + r = s[i] - t[i]; + if (r != 0) + break; + } + if (r >= 0) { + if (r == 0 && UNIQUE) { + /* Put record below existing */ + ai[1] = ai[0]; + /* Mark as duplicate - ignore */ + akh->keylen = -1; + } else { + ai++; + } + break; + } + } + ai[1] = ai[0]; + if (ai == a) + break; + } + ai[0] = akh; + } +} diff --git a/usr.bin/sort/sort.1 b/usr.bin/sort/sort.1 new file mode 100644 index 0000000..548aafd --- /dev/null +++ b/usr.bin/sort/sort.1 @@ -0,0 +1,525 @@ +.\" $NetBSD: sort.1,v 1.38 2017/07/03 21:34:21 wiz Exp $ +.\" +.\" Copyright (c) 2000-2003 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Ben Harris and Jaromir Dolecek. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)sort.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 1, 2016 +.Dt SORT 1 +.Os +.Sh NAME +.Nm sort +.Nd sort or merge text files +.Sh SYNOPSIS +.Nm +.Op Fl bdfHilmnrSsu +.Oo +.Fl k +.Ar kstart Ns Op Li \&, Ns Ar kend +.Oc +.Op Fl o Ar output +.Op Fl R Ar char +.Op Fl T Ar dir +.Op Fl t Ar char +.Op Ar +.Nm +.Fl C Ns | Ns Fl c +.Op Fl bdfilnru +.Oo +.Fl k +.Ar kstart Ns Op Li \&, Ns Ar kend +.Op Fl t Ar char +.Oc +.Op Fl R Ar char +.Op Ar file +.Sh DESCRIPTION +The +.Nm +utility sorts text files by lines. +Comparisons are based on one or more sort keys extracted +from each line of input, and are performed lexicographically. +By default, if keys are not given, +.Nm +regards each input line as a single field. +.Pp +The following options are available: +.Bl -tag -width Fl +.It Fl C +Identical to +.Fl c +without the error messages in the case of unsorted input. +.It Fl c +Check that the single input file is sorted. +If the file is not sorted, +.Nm +produces the appropriate error messages and exits with code 1; otherwise, +.Nm +returns 0. +.Nm +.Fl c +produces no output. +See also +.Fl u . +.It Fl H +Ignored for compatibility with earlier versions of +.Nm . +.It Fl m +Merge only; the input files are assumed to be pre-sorted. +.It Fl o Ar output +The argument given is the name of an +.Ar output +file to be used instead of the standard output. +This file can be the same as one of the input files. +.It Fl S +Don't use stable sort. +Default is to use stable sort. +.It Fl s +Use stable sort, keeps records with equal keys in their original order. +This is the default. +Provided for compatibility with other +.Nm +implementations only. +.It Fl T Ar dir +Use +.Ar dir +as the directory for temporary files. +The default is the value specified in the environment variable +.Ev TMPDIR or +.Pa /tmp +if +.Ev TMPDIR +is not defined. +.It Fl u +Unique: suppress all but one in each set of lines having equal keys. +If used with the +.Fl c +option, check that there are no lines with duplicate keys. +.El +.Pp +The following options, +which should be given before any +.Fl k +options, override the default ordering rules. +When ordering options appear independent of, +and before, key field specifications, +the requested field ordering rules are +applied globally to all sort keys. +When attached to a specific key (see +.Fl k ) , +the ordering options override +all global ordering options for that key. +.Bl -tag -width Fl +.It Fl d +Only blank space and alphanumeric characters +.\" according +.\" to the current setting of LC_CTYPE +are used +in making comparisons. +.It Fl f +Considers all lowercase characters that have uppercase +equivalents to be the same for purposes of comparison. +.It Fl i +Ignore all non-printable characters. +.It Fl l +Sort by the string length of the field, not by the field itself. +.It Fl n +An initial numeric string, consisting of optional blank space, optional +plus or minus sign, and zero or more digits (including decimal point) +.\" with +.\" optional radix character and thousands +.\" separator +.\" (as defined in the current locale), +is sorted by arithmetic value. +(The +.Fl n +option no longer implies the +.Fl b +option.) +.It Fl r +Reverse the sense of comparisons. +.El +.Pp +The treatment of field separators can be altered using these options: +.Bl -tag -width Fl +.It Fl b +Ignores leading blank space when determining the start +and end of a restricted sort key. +A +.Fl b +option specified before the first +.Fl k +option applies globally to all +.Fl k +options. +Otherwise, the +.Fl b +option can be attached independently to each +.Ar field +argument of the +.Fl k +option (see below). +Note that the +.Fl b +option has no effect unless key fields are specified. +.It Fl k Ar kstart Ns Op Li \&, Ns Ar kend +Designates the starting position, +.Ar kstart , +and optional ending position, +.Ar kend , +of a key field. +The +.Fl k +option replaces the obsolescent options +.Cm \(pl Ns Ar pos1 +and +.Fl Ns Ar pos2 . +.It Fl R Ar char +.Ar char +is used as the record separator character. +This should be used with discretion; +.Fl R Aq Ar alphanumeric +usually produces undesirable results. +If char is not a single character, then it +specifies the value of the desired record +separator as an integer specified in any +of the normal NNN, 0ooo, or 0xXXX ways, +or as an octal value preceded by \e. +Caution: do not attempt to specify Ctl-A +as +.Dq -R 1 +which will not do what was intended at all! +The default record separator is newline. +.It Fl t Ar char +.Ar char +is used as the field separator character. +The initial +.Ar char +is not considered to be part of a field when determining +key offsets (see below). +Each occurrence of +.Ar char +is significant (for example, +.Dq Ar charchar +delimits an empty field). +If +.Fl t +is not specified, the default field separator is a sequence of +blank-space characters, and consecutive blank spaces do +.Em not +delimit an empty field; further, the initial blank space +.Em is +considered part of a field when determining key offsets. +.El +.Pp +The following operands are available: +.Bl -tag -width Ar +.It Ar file +The pathname of a file to be sorted, merged, or checked. +If no +.Ar file +operands are specified, or if +a +.Ar file +operand is +.Fl , +the standard input is used. +.El +.Pp +A field is defined as a minimal sequence of characters followed by a +field separator or a newline character. +By default, the first +blank space of a sequence of blank spaces acts as the field separator. +All blank spaces in a sequence of blank spaces are considered +as part of the next field; for example, all blank spaces at +the beginning of a line are considered to be part of the +first field. +.Pp +Fields are specified +by the +.Fl k +.Ar kstart Ns Op \&, Ns Ar kend +argument. +A missing +.Ar kend +argument defaults to the end of a line. +.Pp +The arguments +.Ar kstart +and +.Ar kend +have the form +.Ar m Ns Li \&. Ns Ar n +and can be followed by one or more of the letters +.Cm b , d , f , i , +.Cm l , n , +and +.Cm r , +which correspond to the options discussed above. +A +.Ar kstart +position specified by +.Ar m Ns Li \&. Ns Ar n +.Pq Ar m , n No > 0 +is interpreted as the +.Ar n Ns th +character in the +.Ar m Ns th +field. +A missing +.Li \&. Ns Ar n +in +.Ar kstart +means +.Ql \&.1 , +indicating the first character of the +.Ar m Ns th +field; if the +.Fl b +option is in effect, +.Ar n +is counted from the first non-blank character in the +.Ar m Ns th +field; +.Ar m Ns Li \&.1b +refers to the first non-blank character in the +.Ar m Ns th +field. +.Pp +A +.Ar kend +position specified by +.Ar m Ns Li \&. Ns Ar n +is interpreted as +the +.Ar n Ns th +character (including separators) of the +.Ar m Ns th +field. +A missing +.Li \&. Ns Ar n +indicates the last character of the +.Ar m Ns th +field; +.Ar m += \&0 +designates the end of a line. +Thus the option +.Fl k +.Sm off +.Xo +.Ar v Li \&. Ar x Li \&, +.Ar w Li \&. Ar y +.Xc +.Sm on +is synonymous with the obsolescent option +.Sm off +.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1 +.Fl Ar w-\&1 Li \&. Ar y ; +.Sm on +when +.Ar y +is omitted, +.Fl k +.Sm off +.Ar v Li \&. Ar x Li \&, Ar w +.Sm on +is synonymous with +.Sm off +.Cm \(pl Ar v-\&1 Li \&. Ar x-\&1 +.Fl Ar w+1 Li \&.0 . +.Sm on +The obsolescent +.Cm \(pl Ns Ar pos1 +.Fl Ns Ar pos2 +option is still supported, except for +.Fl Ns Ar w Ns Li \&.0b , +which has no +.Fl k +equivalent. +.Pp +.Nm +compares records by comparing the key fields selected by +.Fl k +arguments, +from first given to last, +until discovering a difference. +If there are no +.Fl k +arguments, the whole record is treated as a single key. +After exhausting the +.Fl k +arguments, if no difference has been found, +then the result depends upon the +.Fl u +and +.Fl S +option settings. +With +.Fl u +the records are considered identical, and one is supressed. +Otherwise with +.Fl s +set (default) the records are left in their original order, +or with +.Fl S +(posix mode) the whole record is considered as a tie breaker. +.\" +.\" If you fail to understand why it doesn't matter which order +.\" the records are output when they are wholly identical, there +.\" is nothing that this man page can say that wll help! +.\" +.Sh ENVIRONMENT +If the following environment variable exists, it is used by +.Nm . +.Bl -tag -width Ev +.It Ev TMPDIR +.Nm +uses the contents of the +.Ev TMPDIR +environment variable as the path in which to store +temporary files. +.El +.Sh FILES +.Bl -tag -width outputNUMBER+some -compact +.It Pa /tmp/sort.* +Default temporary files. +.It Ar output Ns NUMBER +Temporary file which is used for output if +.Ar output +already exists. +Once sorting is finished, this file replaces +.Ar output +(via +.Xr link 2 +and +.Xr unlink 2 ) . +.El +.Sh EXIT STATUS +Sort exits with one of the following values: +.Bl -tag -width flag -compact +.It 0 +Normal behavior. +.It 1 +On disorder (or non-uniqueness) with the +.Fl c +(or +.Fl C ) +option. +.It 2 +An error occurred. +.El +.Sh SEE ALSO +.Xr comm 1 , +.Xr join 1 , +.Xr uniq 1 , +.Xr qsort 3 , +.Xr radixsort 3 +.Sh HISTORY +A +.Nm +command appeared in +.At v5 . +This +.Nm +implementation appeared in +.Bx 4.4 +and is used since +.Nx 1.6 . +.Sh BUGS +Posix requires the locale's thousands separator be ignored in numbers. +It may be faster to sort very large files in pieces and then explicitly +merge them. +.Sh NOTES +This +.Nm +has no limits on input line length (other than imposed by available +memory) or any restrictions on bytes allowed within lines. +.Pp +To protect data +.Nm +.Fl o +calls +.Xr link 2 +and +.Xr unlink 2 , +and thus fails on protected directories. +.Pp +Input files should be text files. +If file doesn't end with record separator (which is typically newline), the +.Nm +utility silently supplies one. +.Pp +The current +.Nm +uses lexicographic radix sorting, which requires +that sort keys be kept in memory (as opposed to previous versions which used quick +and merge sorts and did not.) +Thus performance depends highly on efficient choice of sort keys, and the +.Fl b +option and the +.Ar kend +argument of the +.Fl k +option should be used whenever possible. +Similarly, +.Nm +.Fl k1f +is equivalent to +.Nm +.Fl f +and may take twice as long. diff --git a/usr.bin/sort/sort.c b/usr.bin/sort/sort.c new file mode 100644 index 0000000..ee5ffdb --- /dev/null +++ b/usr.bin/sort/sort.c @@ -0,0 +1,419 @@ +/* $NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ +__RCSID("$NetBSD: sort.c,v 1.64 2017/01/10 21:13:45 christos Exp $"); + +/* Sort sorts a file using an optional user-defined key. + * Sort uses radix sort for internal sorting, and allows + * a choice of merge sort and radix sort for external sorting. + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "sort.h" +#include "fsort.h" +#include "pathnames.h" + +int REC_D = '\n'; +u_char d_mask[NBINS]; /* flags for rec_d, field_d, */ + +/* + * weight tables. Gweights is one of ascii, Rascii.. + * modified to weight rec_d = 0 (or 255) + */ +u_char *const weight_tables[4] = { ascii, Rascii, Ftable, RFtable }; +u_char ascii[NBINS], Rascii[NBINS], RFtable[NBINS], Ftable[NBINS]; + +int SINGL_FLD = 0, SEP_FLAG = 0, UNIQUE = 0; +int REVERSE = 0; +int posix_sort; + +unsigned int debug_flags = 0; + +static char toutpath[MAXPATHLEN]; + +const char *tmpdir; /* where temporary files should be put */ + +static void cleanup(void); +static void onsignal(int); +__dead static void usage(const char *); + +int +main(int argc, char *argv[]) +{ + int ch, i, stdinflag = 0; + char mode = 0; + char *outfile, *outpath = 0; + struct field *fldtab; + size_t fldtab_sz, fld_cnt; + struct filelist filelist; + int num_input_files; + FILE *outfp = NULL; + struct rlimit rl; + struct stat st; + + setlocale(LC_ALL, ""); + + /* bump RLIMIT_NOFILE to maximum our hard limit allows */ + if (getrlimit(RLIMIT_NOFILE, &rl) < 0) + err(2, "getrlimit"); + rl.rlim_cur = rl.rlim_max; + if (setrlimit(RLIMIT_NOFILE, &rl) < 0) + err(2, "setrlimit"); + + d_mask[REC_D = '\n'] = REC_D_F; + d_mask['\t'] = d_mask[' '] = BLANK | FLD_D; + + /* fldtab[0] is the global options. */ + fldtab_sz = 3; + fld_cnt = 0; + fldtab = emalloc(fldtab_sz * sizeof(*fldtab)); + memset(fldtab, 0, fldtab_sz * sizeof(*fldtab)); + +#define SORT_OPTS "bcCdD:fHik:lmno:rR:sSt:T:u" + + /* Convert "+field" args to -k format */ + fixit(&argc, argv, SORT_OPTS); + + if (!(tmpdir = getenv("TMPDIR"))) + tmpdir = _PATH_TMP; + + while ((ch = getopt(argc, argv, SORT_OPTS)) != -1) { + switch (ch) { + case 'b': + fldtab[0].flags |= BI | BT; + break; + case 'c': case 'C': case 'm': + if (mode) + usage("Incompatible operation modes"); + mode = ch; + break; + case 'D': /* Debug flags */ + for (i = 0; optarg[i]; i++) + debug_flags |= 1 << (optarg[i] & 31); + break; + case 'd': case 'f': case 'i': case 'n': case 'l': + fldtab[0].flags |= optval(ch, 0); + break; + case 'H': + /* -H was ; use merge sort for blocks of large files' */ + /* That is now the default. */ + break; + case 'k': + fldtab = erealloc(fldtab, (fldtab_sz + 1) * sizeof(*fldtab)); + memset(&fldtab[fldtab_sz], 0, sizeof(fldtab[0])); + fldtab_sz++; + + setfield(optarg, &fldtab[++fld_cnt], fldtab[0].flags); + break; + case 'o': + outpath = optarg; + break; + case 'r': + REVERSE = 1; + break; + case 'R': + if (REC_D != '\n') + usage("multiple record delimiters"); + REC_D = *optarg; + if (optarg[1] != '\0') { + char *ep; + int t = 0; + + if (optarg[0] == '\\') + optarg++, t = 8; + REC_D = (int)strtol(optarg, &ep, t); + if (*ep != '\0' || REC_D < 0 || + REC_D >= (int)__arraycount(d_mask)) + errx(2, "invalid record delimiter %s", + optarg); + } + if (REC_D == '\n') + break; + d_mask['\n'] = d_mask[' ']; + d_mask[REC_D] = REC_D_F; + break; + case 's': + /* + * Nominally 'stable sort', keep lines with equal keys + * in input file order. (Default for NetBSD) + * (-s for GNU sort compatibility.) + */ + posix_sort = 0; + break; + case 'S': + /* + * Reverse of -s! + * This needs to enforce a POSIX sort where records + * with equal keys are then sorted by the raw data. + * Currently not implemented! + * (using libc radixsort() v sradixsort() doesn't + * have the desired effect.) + */ + posix_sort = 1; + break; + case 't': + if (SEP_FLAG) + usage("multiple field delimiters"); + SEP_FLAG = 1; + d_mask[' '] &= ~FLD_D; + d_mask['\t'] &= ~FLD_D; + d_mask['\n'] &= ~FLD_D; + d_mask[(u_char)*optarg] |= FLD_D; + if (d_mask[(u_char)*optarg] & REC_D_F) + errx(2, "record/field delimiter clash"); + break; + case 'T': + /* -T tmpdir */ + tmpdir = optarg; + break; + case 'u': + UNIQUE = 1; + break; + case '?': + default: + usage(NULL); + } + } + + if (UNIQUE) + /* Don't sort on raw record if keys match */ + posix_sort = 0; + + if ((mode == 'c' || mode == 'C') && argc > optind+1) + errx(2, "too many input files for -c option"); + if (argc - 2 > optind && !strcmp(argv[argc-2], "-o")) { + outpath = argv[argc-1]; + argc -= 2; + } + if (mode == 'm' && argc - optind > (MAXFCT - (16+1))*16) + errx(2, "too many input files for -m option"); + + for (i = optind; i < argc; i++) { + /* allow one occurrence of /dev/stdin */ + if (!strcmp(argv[i], "-") || !strcmp(argv[i], _PATH_STDIN)) { + if (stdinflag) + warnx("ignoring extra \"%s\" in file list", + argv[i]); + else + stdinflag = 1; + + /* change to /dev/stdin if '-' */ + if (argv[i][0] == '-') { + static char path_stdin[] = _PATH_STDIN; + argv[i] = path_stdin; + } + + } else if ((ch = access(argv[i], R_OK))) + err(2, "%s", argv[i]); + } + + if (fldtab[1].icol.num == 0) { + /* No sort key specified */ + if (fldtab[0].flags & (I|D|F|N|L)) { + /* Modified - generate a key that covers the line */ + fldtab[0].flags &= ~(BI|BT); + setfield("1", &fldtab[++fld_cnt], fldtab->flags); + fldreset(fldtab); + } else { + /* Unmodified, just compare the line */ + SINGL_FLD = 1; + fldtab[0].icol.num = 1; + } + } else { + fldreset(fldtab); + } + + settables(); + + if (optind == argc) { + static const char * const names[] = { _PATH_STDIN, NULL }; + filelist.names = names; + num_input_files = 1; + } else { + filelist.names = (const char * const *) &argv[optind]; + num_input_files = argc - optind; + } + + if (mode == 'c' || mode == 'C') { + order(&filelist, fldtab, mode == 'C'); + /* NOT REACHED */ + } + + if (!outpath) { + toutpath[0] = '\0'; /* path not used in this case */ + outfile = outpath = toutpath; + outfp = stdout; + } else if (lstat(outpath, &st) == 0 + && !S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + /* output file exists and isn't character or block device */ + struct sigaction act; + static const int sigtable[] = {SIGHUP, SIGINT, SIGPIPE, + SIGXCPU, SIGXFSZ, SIGVTALRM, SIGPROF, 0}; + int outfd; + errno = 0; + if (access(outpath, W_OK)) + err(2, "%s", outpath); + (void)snprintf(toutpath, sizeof(toutpath), "%sXXXXXX", + outpath); + if ((outfd = mkstemp(toutpath)) == -1) + err(2, "Cannot create temporary file `%s'", toutpath); + (void)atexit(cleanup); + act.sa_handler = onsignal; + (void) sigemptyset(&act.sa_mask); + act.sa_flags = SA_RESTART | SA_RESETHAND; + for (i = 0; sigtable[i]; ++i) /* always unlink toutpath */ + sigaction(sigtable[i], &act, 0); + outfile = toutpath; + if ((outfp = fdopen(outfd, "w")) == NULL) + err(2, "Cannot open temporary file `%s'", toutpath); + } else { + outfile = outpath; + + if ((outfp = fopen(outfile, "w")) == NULL) + err(2, "output file %s", outfile); + } + + if (mode == 'm') + fmerge(&filelist, num_input_files, outfp, fldtab); + else + fsort(&filelist, num_input_files, outfp, fldtab); + + if (outfile != outpath) { + if (access(outfile, F_OK)) + err(2, "%s", outfile); + + /* + * Copy file permissions bits of the original file. + * st is initialized above, when we create the + * temporary spool file. + */ + if (lchmod(outfile, st.st_mode & ALLPERMS) != 0) { + err(2, "cannot chmod %s: output left in %s", + outpath, outfile); + } + + (void)unlink(outpath); + if (link(outfile, outpath)) + err(2, "cannot link %s: output left in %s", + outpath, outfile); + (void)unlink(outfile); + toutpath[0] = 0; + } + exit(0); +} + +static void +onsignal(int sig) +{ + cleanup(); +} + +static void +cleanup(void) +{ + if (toutpath[0]) + (void)unlink(toutpath); +} + +static void +usage(const char *msg) +{ + const char *pn = getprogname(); + + if (msg != NULL) + (void)fprintf(stderr, "%s: %s\n", getprogname(), msg); + (void)fprintf(stderr, + "usage: %s [-bdfHilmnrSsu] [-k kstart[,kend]] [-o output]" + " [-R char] [-T dir]\n", pn); + (void)fprintf(stderr, + " [-t char] [file ...]\n"); + (void)fprintf(stderr, + " or: %s -C|-c [-bdfilnru] [-k kstart[,kend]] [-o output]" + " [-R char]\n", pn); + (void)fprintf(stderr, + " [-t char] [file]\n"); + exit(2); +} + +RECHEADER * +allocrec(RECHEADER *rec, size_t size) +{ + + return (erealloc(rec, size + sizeof(long) - 1)); +} diff --git a/usr.bin/sort/sort.h b/usr.bin/sort/sort.h new file mode 100644 index 0000000..d7a3144 --- /dev/null +++ b/usr.bin/sort/sort.h @@ -0,0 +1,201 @@ +/* $NetBSD: sort.h,v 1.36 2016/06/01 02:37:55 kre Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)sort.h 8.1 (Berkeley) 6/6/93 + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NBINS 256 + +/* values for masks, weights, and other flags. */ +/* R and F get used to index weight_tables[] */ +#define R 0x01 /* Field is reversed */ +#define F 0x02 /* weight lower and upper case the same */ +#define I 0x04 /* mask out non-printable characters */ +#define D 0x08 /* sort alphanumeric characters only */ +#define N 0x10 /* Field is a number */ +#define BI 0x20 /* ignore blanks in icol */ +#define BT 0x40 /* ignore blanks in tcol */ +#define L 0x80 /* Sort by field length */ + +/* masks for delimiters: blanks, fields, and termination. */ +#define BLANK 1 /* ' ', '\t'; '\n' if -R is invoked */ +#define FLD_D 2 /* ' ', '\t' default; from -t otherwise */ +#define REC_D_F 4 /* '\n' default; from -R otherwise */ + +#define min(a, b) ((a) < (b) ? (a) : (b)) +#define max(a, b) ((a) > (b) ? (a) : (b)) + +#define FCLOSE(file) { \ + if (EOF == fclose(file)) \ + err(2, "%p", file); \ +} + +#define EWRITE(ptr, size, n, f, fmt) { \ + if (!fwrite(ptr, size, n, f)) \ + err(2, fmt); \ +} + +/* Records are limited to MAXBUFSIZE (8MB) and less if you want to sort + * in a sane way. + * Anyone who wants to sort data records longer than 2GB definitely needs a + * different program! */ +typedef unsigned int length_t; + +/* A record is a key/line pair starting at rec.data. It has a total length + * and an offset to the start of the line half of the pair. + */ +typedef struct recheader { + length_t length; /* total length of key and line */ + length_t offset; /* to line */ + int keylen; /* length of key */ + u_char data[]; /* key then line */ +} RECHEADER; + +/* This is the column as seen by struct field. It is used by enterfield. + * They are matched with corresponding coldescs during initialization. + */ +struct column { + struct coldesc *p; + int num; + int indent; +}; + +/* a coldesc has a number and pointers to the beginning and end of the + * corresponding column in the current line. This is determined in enterkey. + */ +typedef struct coldesc { + u_char *start; + u_char *end; + int num; +} COLDESC; + +/* A field has an initial and final column; an omitted final column + * implies the end of the line. Flags regulate omission of blanks and + * numerical sorts; mask determines which characters are ignored (from -i, -d); + * weights determines the sort weights of a character (from -f, -r). + * + * The first field contain the global flags etc. + * The list terminates when icol = 0. + */ +struct field { + struct column icol; + struct column tcol; + u_int flags; + u_char *mask; + u_char *weights; +}; + +struct filelist { + const char * const * names; +}; + +typedef int (*get_func_t)(FILE *, RECHEADER *, u_char *, struct field *); +typedef void (*put_func_t)(const RECHEADER *, FILE *); + +extern u_char ascii[NBINS], Rascii[NBINS], Ftable[NBINS], RFtable[NBINS]; +extern u_char *const weight_tables[4]; /* ascii, Rascii, Ftable, RFtable */ +extern u_char d_mask[NBINS]; +extern int SINGL_FLD, SEP_FLAG, UNIQUE, REVERSE; +extern int posix_sort; +extern int REC_D; +extern const char *tmpdir; +extern struct coldesc *clist; +extern int ncols; + +#define DEBUG(ch) (debug_flags & (1 << ((ch) & 31))) +extern unsigned int debug_flags; + +RECHEADER *allocrec(RECHEADER *, size_t); +void append(RECHEADER **, int, FILE *, void (*)(const RECHEADER *, FILE *)); +void concat(FILE *, FILE *); +length_t enterkey(RECHEADER *, const u_char *, u_char *, size_t, struct field *); +void fixit(int *, char **, const char *); +void fldreset(struct field *); +FILE *ftmp(void); +void fmerge(struct filelist *, int, FILE *, struct field *); +void save_for_merge(FILE *, get_func_t, struct field *); +void merge_sort(FILE *, put_func_t, struct field *); +void fsort(struct filelist *, int, FILE *, struct field *); +int geteasy(FILE *, RECHEADER *, u_char *, struct field *); +int makekey(FILE *, RECHEADER *, u_char *, struct field *); +int makeline(FILE *, RECHEADER *, u_char *, struct field *); +void makeline_copydown(RECHEADER *); +int optval(int, int); +__dead void order(struct filelist *, struct field *, int); +void putline(const RECHEADER *, FILE *); +void putrec(const RECHEADER *, FILE *); +void putkeydump(const RECHEADER *, FILE *); +void rd_append(int, int, int, FILE *, u_char *, u_char *); +void radix_sort(RECHEADER **, RECHEADER **, int); +int setfield(const char *, struct field *, int); +void settables(void); diff --git a/usr.bin/sort/tmp.c b/usr.bin/sort/tmp.c new file mode 100644 index 0000000..155b3f5 --- /dev/null +++ b/usr.bin/sort/tmp.c @@ -0,0 +1,106 @@ +/* $NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $ */ + +/*- + * Copyright (c) 2000-2003 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Ben Harris and Jaromir Dolecek. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Peter McIlroy. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +__RCSID("$NetBSD: tmp.c,v 1.16 2009/11/06 18:34:22 joerg Exp $"); + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sort.h" +#include "pathnames.h" + +#define _NAME_TMP "sort.XXXXXXXX" + +FILE * +ftmp(void) +{ + sigset_t set, oset; + FILE *fp; + int fd; + char path[MAXPATHLEN]; + + (void)snprintf(path, sizeof(path), "%s%s%s", tmpdir, + (tmpdir[strlen(tmpdir)-1] != '/') ? "/" : "", _NAME_TMP); + + sigfillset(&set); + (void)sigprocmask(SIG_BLOCK, &set, &oset); + if ((fd = mkstemp(path)) < 0) + err(2, "ftmp: mkstemp(\"%s\")", path); + if (!(fp = fdopen(fd, "w+"))) + err(2, "ftmp: fdopen(\"%s\")", path); + if (!DEBUG('t')) + (void)unlink(path); + + (void)sigprocmask(SIG_SETMASK, &oset, NULL); + return (fp); +} diff --git a/usr.bin/split/split.1 b/usr.bin/split/split.1 new file mode 100644 index 0000000..a25e3c6 --- /dev/null +++ b/usr.bin/split/split.1 @@ -0,0 +1,132 @@ +.\" $NetBSD: split.1,v 1.15 2007/05/31 01:35:35 jschauma Exp $ +.\" +.\" Copyright (c) 1990, 1991, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)split.1 8.3 (Berkeley) 4/16/94 +.\" +.Dd May 28, 2007 +.Dt SPLIT 1 +.Os +.Sh NAME +.Nm split +.Nd split a file into pieces +.Sh SYNOPSIS +.Nm +.Op Fl a Ar suffix_length +.Oo +.Fl b Ar byte_count Ns Oo Li k|m Oc | +.Fl l Ar line_count +.Fl n Ar chunk_count +.Oc +.Op Ar file Op Ar name +.Sh DESCRIPTION +The +.Nm +utility reads the given +.Ar file +and breaks it up into files of 1000 lines each. +If +.Ar file +is a single dash or absent, +.Nm +reads from the standard input. +.Ar file +itself is not altered. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a +Use +.Ar suffix_length +letters to form the suffix of the file name. +.It Fl b +Create smaller files +.Ar byte_count +bytes in length. +If +.Ql k +is appended to the number, the file is split into +.Ar byte_count +kilobyte pieces. +If +.Ql m +is appended to the number, the file is split into +.Ar byte_count +megabyte pieces. +.It Fl l +Create smaller files +.Ar line_count +lines in length. +.It Fl n +Split file into +.Ar chunk_count +smaller files. +.El +.Pp +If additional arguments are specified, the first is used as the name +of the input file which is to be split. +If a second additional argument is specified, it is used as a prefix +for the names of the files into which the file is split. +In this case, each file into which the file is split is named by the +prefix followed by a lexically ordered suffix using +.Ar suffix_length +characters in the range +.Dq Li a-z . +If +.Fl a +is not specified, two letters are used as the suffix. +.Pp +If the +.Ar name +argument is not specified, +.Ql x +is used. +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1-2001 . +.Sh HISTORY +A +.Nm +command appeared in +.At v6 . +.Pp +The +.Fl a +option was introduced in +.Nx 2.0 . +Before that, if +.Ar name +was not specified, +.Nm +would vary the first letter of the filename +to increase the number of possible output files. +The +.Fl a +option makes this unnecessary. diff --git a/usr.bin/split/split.c b/usr.bin/split/split.c new file mode 100644 index 0000000..e538da1 --- /dev/null +++ b/usr.bin/split/split.c @@ -0,0 +1,362 @@ +/* $NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1987, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)split.c 8.3 (Berkeley) 4/25/94"; +#endif +__RCSID("$NetBSD: split.c,v 1.27 2017/01/10 21:14:13 christos Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEFLINE 1000 /* Default num lines per file. */ + +static int file_open; /* If a file open. */ +static int ifd = STDIN_FILENO, ofd = -1; /* Input/output file descriptors. */ +static char *fname; /* File name prefix. */ +static size_t sfxlen = 2; /* suffix length. */ + +static void newfile(void); +static void split1(off_t, int) __dead; +static void split2(off_t) __dead; +static void split3(off_t) __dead; +static void usage(void) __dead; +static size_t bigwrite(int, void const *, size_t); + +int +main(int argc, char *argv[]) +{ + int ch; + char *ep, *p; + char const *base; + off_t bytecnt = 0; /* Byte count to split on. */ + off_t numlines = 0; /* Line count to split on. */ + off_t chunks = 0; /* Number of chunks to split into. */ + + while ((ch = getopt(argc, argv, "0123456789b:l:a:n:")) != -1) + switch (ch) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + /* + * Undocumented kludge: split was originally designed + * to take a number after a dash. + */ + if (numlines == 0) { + p = argv[optind - 1]; + if (p[0] == '-' && p[1] == ch && !p[2]) + p++; + else + p = argv[optind] + 1; + numlines = strtoull(p, &ep, 10); + if (numlines == 0 || *ep != '\0') + errx(1, "%s: illegal line count.", p); + } + break; + case 'b': /* Byte count. */ + if (!isdigit((unsigned char)optarg[0]) || + (bytecnt = strtoull(optarg, &ep, 10)) == 0 || + (*ep != '\0' && *ep != 'k' && *ep != 'm')) + errx(1, "%s: illegal byte count.", optarg); + if (*ep == 'k') + bytecnt *= 1024; + else if (*ep == 'm') + bytecnt *= 1024 * 1024; + break; + case 'l': /* Line count. */ + if (numlines != 0) + usage(); + if (!isdigit((unsigned char)optarg[0]) || + (numlines = strtoull(optarg, &ep, 10)) == 0 || + *ep != '\0') + errx(1, "%s: illegal line count.", optarg); + break; + case 'a': /* Suffix length. */ + if (!isdigit((unsigned char)optarg[0]) || + (sfxlen = (size_t)strtoul(optarg, &ep, 10)) == 0 || + *ep != '\0') + errx(1, "%s: illegal suffix length.", optarg); + break; + case 'n': /* Chunks. */ + if (!isdigit((unsigned char)optarg[0]) || + (chunks = (size_t)strtoul(optarg, &ep, 10)) == 0 || + *ep != '\0') + errx(1, "%s: illegal number of chunks.", optarg); + break; + default: + usage(); + } + argv += optind; + argc -= optind; + + if (*argv != NULL) { + if (strcmp(*argv, "-") != 0 && + (ifd = open(*argv, O_RDONLY, 0)) < 0) + err(1, "%s", *argv); + ++argv; + } + + + base = (*argv != NULL) ? *argv++ : "x"; + if ((fname = malloc(strlen(base) + sfxlen + 1)) == NULL) + err(EXIT_FAILURE, NULL); + (void)strcpy(fname, base); /* File name prefix. */ + + if (*argv != NULL) + usage(); + + if (numlines == 0) + numlines = DEFLINE; + else if (bytecnt || chunks) + usage(); + + if (bytecnt && chunks) + usage(); + + if (bytecnt) + split1(bytecnt, 0); + else if (chunks) + split3(chunks); + else + split2(numlines); + + return 0; +} + +/* + * split1 -- + * Split the input by bytes. + */ +static void +split1(off_t bytecnt, int maxcnt) +{ + off_t bcnt; + ssize_t dist, len; + char *C; + char bfr[MAXBSIZE]; + int nfiles; + + nfiles = 0; + + for (bcnt = 0;;) + switch (len = read(ifd, bfr, MAXBSIZE)) { + case 0: + exit(0); + /* NOTREACHED */ + case -1: + err(1, "read"); + /* NOTREACHED */ + default: + if (!file_open) { + if (!maxcnt || (nfiles < maxcnt)) { + newfile(); + nfiles++; + file_open = 1; + } + } + if (bcnt + len >= bytecnt) { + /* LINTED: bytecnt - bcnt <= len */ + dist = bytecnt - bcnt; + if (bigwrite(ofd, bfr, dist) != (size_t)dist) + err(1, "write"); + len -= dist; + for (C = bfr + dist; len >= bytecnt; + /* LINTED: bytecnt <= len */ + len -= bytecnt, C += bytecnt) { + if (!maxcnt || (nfiles < maxcnt)) { + newfile(); + nfiles++; + } + /* LINTED: as above */ + if (bigwrite(ofd, + C, bytecnt) != (size_t)bytecnt) + err(1, "write"); + } + if (len) { + if (!maxcnt || (nfiles < maxcnt)) { + newfile(); + nfiles++; + } + /* LINTED: len >= 0 */ + if (bigwrite(ofd, C, len) != (size_t)len) + err(1, "write"); + } else + file_open = 0; + bcnt = len; + } else { + bcnt += len; + /* LINTED: len >= 0 */ + if (bigwrite(ofd, bfr, len) != (size_t)len) + err(1, "write"); + } + } +} + +/* + * split2 -- + * Split the input by lines. + */ +static void +split2(off_t numlines) +{ + off_t lcnt; + size_t bcnt; + ssize_t len; + char *Ce, *Cs; + char bfr[MAXBSIZE]; + + for (lcnt = 0;;) + switch (len = read(ifd, bfr, MAXBSIZE)) { + case 0: + exit(0); + /* NOTREACHED */ + case -1: + err(1, "read"); + /* NOTREACHED */ + default: + if (!file_open) { + newfile(); + file_open = 1; + } + for (Cs = Ce = bfr; len--; Ce++) + if (*Ce == '\n' && ++lcnt == numlines) { + bcnt = Ce - Cs + 1; + if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt) + err(1, "write"); + lcnt = 0; + Cs = Ce + 1; + if (len) + newfile(); + else + file_open = 0; + } + if (Cs < Ce) { + bcnt = Ce - Cs; + if (bigwrite(ofd, Cs, bcnt) != (size_t)bcnt) + err(1, "write"); + } + } +} + +/* + * split3 -- + * Split the input into specified number of chunks + */ +static void +split3(off_t chunks) +{ + struct stat sb; + + if (fstat(ifd, &sb) == -1) { + err(1, "stat"); + /* NOTREACHED */ + } + + if (chunks > sb.st_size) { + errx(1, "can't split into more than %d files", + (int)sb.st_size); + /* NOTREACHED */ + } + + split1(sb.st_size/chunks, chunks); +} + +/* + * newfile -- + * Open a new output file. + */ +static void +newfile(void) +{ + static int fnum; + static char *fpnt; + int quot, i; + + if (ofd == -1) { + fpnt = fname + strlen(fname); + fpnt[sfxlen] = '\0'; + } else if (close(ofd) != 0) + err(1, "%s", fname); + + quot = fnum; + for (i = sfxlen - 1; i >= 0; i--) { + fpnt[i] = quot % 26 + 'a'; + quot = quot / 26; + } + if (quot > 0) + errx(1, "too many files."); + ++fnum; + if ((ofd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, DEFFILEMODE)) < 0) + err(1, "%s", fname); +} + +static size_t +bigwrite(int fd, const void *buf, size_t len) +{ + const char *ptr = buf; + size_t sofar = 0; + ssize_t w; + + while (len != 0) { + if ((w = write(fd, ptr, len)) == -1) + return sofar; + len -= w; + ptr += w; + sofar += w; + } + return sofar; +} + + +static void +usage(void) +{ + (void)fprintf(stderr, +"usage: %s [-b byte_count] [-l line_count] [-n chunk_count] [-a suffix_length] " +"[file [prefix]]\n", getprogname()); + exit(1); +} diff --git a/usr.bin/tabs/tabs.1 b/usr.bin/tabs/tabs.1 new file mode 100644 index 0000000..e88b29a --- /dev/null +++ b/usr.bin/tabs/tabs.1 @@ -0,0 +1,166 @@ +.\" $NetBSD: tabs.1,v 1.6 2014/03/18 18:20:45 riastradh Exp $ +.\" +.\" Copyright (c) 2008 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Roy Marples. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd April 5, 2012 +.Dt TABS 1 +.Os +.Sh NAME +.Nm tabs +.Nd set terminal tabs +.Sh SYNOPSIS +.Nm +.Op Fl n Ns | Ns Fl a Ns | Ns Fl a2 Ns | Ns Fl c Ns | Ns Fl c2 \ +Ns | Ns Fl c3 Ns | Ns Fl f Ns | Ns Fl p Ns | Ns Fl s Ns | Ns Fl u +.Op +m Ns Op n +.Op Fl T Ar type +.Nm +.Op Fl T Ar type +.Op + Ns Op n +.Ar n1 Ns Op , Ns Ar n2 , Ns Ar ... +.Sh DESCRIPTION +The +.Nm +utility displays a series of characters that first clears the hardware terminal +tab settings and then initializes the tab stops at the specified positions +and optionally adjusts the margin. +.Pp +The phrase "tab-stop position N" means that, from the start of a line of +output, tabbing to position N shall cause the next character output to be in +the (N+1)th column on that line. +.Pp +The following options are supported: +.Bl -tag -width Fl +.It Fl Ar n +Specifies repetitive tab stops separated by a uniform number of columns, +.Ar n , +where +.Ar n +is a single digit decimal number. +The default usage of +.Nm +with no arguments is equivalent to +.Nm +.Fl 8 . +When +.Fl 0 +is used, the tab stops are cleared and no new ones set. +.It Fl a +Assembler, applicable to some mainframes. +Equivalent to +.Nm +1,10,16,36,72 . +.It Fl a2 +Assembler, applicable to some mainframes. +Equivalent to +.Nm +1,10,16,40,72 +.It Fl c +.Tn COBOL , +normal format. +Equivalent to +.Nm +1,8,12,16,20,55 +.It Fl c2 +.Tn COBOL , +compact format (columns 1 to 6 omitted). +Equivalent to +.Nm +1,6,10,14,49 +.It Fl c3 +.Tn COBOL , +compact format (columns 1 to 6 omitted), with more tabs than +.Fl c2 . +Equivalent to +.Nm +1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67 +.It Fl f +.Tn FORTRAN . +Equivalent to +.Nm +1,7,11,15,19,23 +.It Fl p +.Tn PL/1 . +Equivalent to +.Nm +1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61 +.It Fl s +.Tn SNOBOL . +Equivalent to +.Nm +1,10,55 +.It Fl T Ar type +Indicates the type of terminal. +.It Fl u +Assembler, applicable to some mainframes. +Equivalent to +.Nm +1,12,20,44 +.El +.Sh ENVIRONMENT +The +.Ev COLUMNS +and +.Ev TERM +environment variables affect the execution of +.Nm +as described in +.Xr environ 7 . +.Pp +The +.Fl T +option overrides +.Ev TERM . +If neither +.Ev TERM +nor the +.Fl T +option are present, +.Nm +will fail. +.Sh EXIT STATUS +.Ex -std +.Sh SEE ALSO +.Xr expand 1 , +.Xr stty 1 , +.Xr tput 1 , +.Xr unexpand 1 , +.Xr terminfo 5 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.1 . +.Sh HISTORY +A +.Nm +utility first appeared in PWB UNIX. +This implementation was introduced in +.Nx 6.0 . +.Sh AUTHORS +.An Roy Marples Aq Mt roy@NetBSD.org diff --git a/usr.bin/tabs/tabs.c b/usr.bin/tabs/tabs.c new file mode 100644 index 0000000..9e017a0 --- /dev/null +++ b/usr.bin/tabs/tabs.c @@ -0,0 +1,232 @@ +/* $NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Roy Marples. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 2008 \ +The NetBSD Foundation, inc. All rights reserved."); +__RCSID("$NetBSD: tabs.c,v 1.5 2019/02/01 08:29:04 mrg Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define NSTOPS 20 + +struct tabspec { + const char *opt; + const char *spec; +}; +static const struct tabspec tabspecs[] = { + {"a", "1,10,16,36,72"}, + {"a2", "1,10,16,40,72"}, + {"c", "1,8,12,16,20,55"}, + {"c2", "1,6,10,14,49"}, + {"c3", "1,6,10,14,18,22,26,30,34,38,42,46,50,54,58,62,67"}, + {"f", "1,7,11,15,19,23"}, + {"p", "1,5,9,13,17,21,25,29,33,37,41,45,49,53,57,61"}, + {"s", "1,10,55"}, + {"u", "1,12,20,44"} +}; +static const size_t ntabspecs = sizeof(tabspecs) / sizeof(tabspecs[0]); + +__dead static void +usage(void) +{ + fprintf(stderr, + "usage: tabs [-n|-a|-a2|-c|-c2|-c3|-f|-p|-s|-u] [+m[n]]" + " [-T type]\n" + " tabs [-T type] [+[n]] n1[,n2,...]\n"); + exit(EXIT_FAILURE); + /* NOTREACHED */ +} + +int +main(int argc, char **argv) +{ + char *term, *arg, *token, *end, *tabs = NULL, *p; + const char *cr, *spec = NULL; + int i, n, inc = 8, stops[NSTOPS], nstops, last, cols, margin = 0; + size_t j; + struct winsize ws; + + term = getenv("TERM"); + for (i = 1; i < argc; i++) { + if (argv[i][0] == '+') { + arg = argv[i] + 1; + if (arg[0] == 'm') + arg++; + if (arg[0] == '\0') + margin = 10; + else { + errno = 0; + margin = strtol(arg, &end, 10); + if (errno != 0 || *end != '\0' || margin < 0) + errx(EXIT_FAILURE, + "%s: invalid margin", arg); + } + continue; + } + if (argv[i][0] != '-') { + tabs = argv[i]; + break; + } + arg = argv[i] + 1; + if (arg[0] == '\0') + usage(); + if (arg[0] == '-' && arg[1] == '\0') { + if (argv[i + 1] != NULL) + tabs = argv[i + 1]; + break; + } + if (arg[0] == 'T' && arg[1] == '\0') { + term = argv[++i]; + if (term == NULL) + usage(); + continue; + } + if (isdigit((int)arg[0])) { + if (arg[1] != '\0') + errx(EXIT_FAILURE, + "%s: invalid increament", arg); + inc = arg[0] - '0'; + continue; + } + for (j = 0; j < ntabspecs; j++) { + if (arg[0] == tabspecs[j].opt[0] && + arg[1] == tabspecs[j].opt[1]) { + spec = tabspecs[j].spec; + break; + } + } + if (j == ntabspecs) + usage(); + } + if (tabs == NULL && spec != NULL) + tabs = strdup(spec); + + if (tabs != NULL) + last = nstops = 0; + else + nstops = -1; + p = tabs; + while ((token = strsep(&p, ", ")) != NULL) { + if (*token == '\0') + continue; + if (nstops >= NSTOPS) + errx(EXIT_FAILURE, + "too many tab stops (max %d)", NSTOPS); + errno = 0; + n = strtol(token, &end, 10); + if (errno != 0 || *end != '\0' || n <= 0) + errx(EXIT_FAILURE, "%s: invalid tab stop", token); + if (*token == '+') { + if (nstops == 0) + errx(EXIT_FAILURE, + "first tab stop may not be relative"); + n += last; + } + if (last > n) + errx(EXIT_FAILURE, "tab stops may not go backwards"); + last = stops[nstops++] = n; + } + + if (term == NULL) + errx(EXIT_FAILURE, "no value for $TERM and -T not given"); + if (setupterm(term, STDOUT_FILENO, NULL) != 0) + err(EXIT_FAILURE, "setupterm:"); + cr = carriage_return; + if (cr == NULL) + cr = "\r"; + if (clear_all_tabs == NULL) + errx(EXIT_FAILURE, "terminal cannot clear tabs"); + if (set_tab == NULL) + errx(EXIT_FAILURE, "terminal cannot set tabs"); + + /* Clear existing tabs */ + putp(cr); + putp(clear_all_tabs); + putp(cr); + + if (set_lr_margin != NULL) { + printf("%*s", margin, ""); + putp(set_lr_margin); + } else if (margin != 0) + warnx("terminal cannot set left margin"); + + if (nstops >= 0) { + printf("%*s", stops[0] - 1, ""); + putp(set_tab); + for (i = 1; i < nstops; i++) { + printf("%*s", stops[i] - stops[i - 1], ""); + putp(set_tab); + } + } else if (inc > 0) { + cols = 0; + term = getenv("COLUMNS"); + if (term != NULL) { + errno = 0; + cols = strtol(term, &end, 10); + if (errno != 0 || *end != '\0' || cols < 0) + cols = 0; + } + if (cols == 0) { + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) + cols = ws.ws_col; + else { + cols = tigetnum("cols"); + if (cols == 0) { + cols = 80; + warnx("terminal does not specify number" + "columns; defaulting to %d", + cols); + } + } + } + for (i = 0; i < cols / inc; i++) { + printf("%*s", inc, ""); + putp(set_tab); + } + } + putp(cr); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} diff --git a/usr.bin/tail/extern.h b/usr.bin/tail/extern.h new file mode 100644 index 0000000..24507b7 --- /dev/null +++ b/usr.bin/tail/extern.h @@ -0,0 +1,52 @@ +/* $NetBSD: extern.h,v 1.10 2011/09/03 09:02:20 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +#define WR(p, size) \ + if (write(STDOUT_FILENO, p, size) != size) \ + oerr(); + +enum STYLE { NOTSET = 0, FBYTES, FLINES, RBYTES, RLINES, REVERSE }; + +void forward(FILE *, enum STYLE, off_t, struct stat *); +void reverse(FILE *, enum STYLE, off_t, struct stat *); + +int displaybytes(FILE *, off_t); +int displaylines(FILE *, off_t); + +void xerr(int fatal, const char *fmt, ...) __printflike(2, 3); +void xerrx(int fatal, const char *fmt, ...) __printflike(2, 3); +void ierr(void); +void oerr(void); + +extern int fflag, rflag, rval; +extern const char *fname; diff --git a/usr.bin/tail/forward.c b/usr.bin/tail/forward.c new file mode 100644 index 0000000..e57e8b5 --- /dev/null +++ b/usr.bin/tail/forward.c @@ -0,0 +1,355 @@ +/* $NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Edward Sze-Tyan Wang. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)forward.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: forward.c,v 1.33 2015/10/09 17:51:26 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +static int rlines(FILE *, off_t, struct stat *); + +/* defines for inner loop actions */ +#define USE_SLEEP 0 +#define USE_KQUEUE 1 +#define ADD_EVENTS 2 + +/* + * forward -- display the file, from an offset, forward. + * + * There are eight separate cases for this -- regular and non-regular + * files, by bytes or lines and from the beginning or end of the file. + * + * FBYTES byte offset from the beginning of the file + * REG seek + * NOREG read, counting bytes + * + * FLINES line offset from the beginning of the file + * REG read, counting lines + * NOREG read, counting lines + * + * RBYTES byte offset from the end of the file + * REG seek + * NOREG cyclically read characters into a wrap-around buffer + * + * RLINES + * REG mmap the file and step back until reach the correct offset. + * NOREG cyclically read lines into a wrap-around array of buffers + */ +void +forward(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ + int ch, n; + int kq=-1, action=USE_SLEEP; + struct stat statbuf; + struct kevent ev[2]; + + switch(style) { + case FBYTES: + if (off == 0) + break; + if (S_ISREG(sbp->st_mode)) { + if (sbp->st_size < off) + off = sbp->st_size; + if (fseeko(fp, off, SEEK_SET) == -1) { + ierr(); + return; + } + } else while (off--) + if ((ch = getc(fp)) == EOF) { + if (ferror(fp)) { + ierr(); + return; + } + break; + } + break; + case FLINES: + if (off == 0) + break; + for (;;) { + if ((ch = getc(fp)) == EOF) { + if (ferror(fp)) { + ierr(); + return; + } + break; + } + if (ch == '\n' && !--off) + break; + } + break; + case RBYTES: + if (S_ISREG(sbp->st_mode)) { + if (sbp->st_size >= off && + fseeko(fp, -off, SEEK_END) == -1) { + ierr(); + return; + } + } else if (off == 0) { + while (getc(fp) != EOF); + if (ferror(fp)) { + ierr(); + return; + } + } else { + if (displaybytes(fp, off)) + return; + } + break; + case RLINES: + if (S_ISREG(sbp->st_mode)) { + if (!off) { + if (fseek(fp, 0L, SEEK_END) == -1) { + ierr(); + return; + } + } else { + if (rlines(fp, off, sbp)) + return; + } + } else if (off == 0) { + while (getc(fp) != EOF); + if (ferror(fp)) { + ierr(); + return; + } + } else { + if (displaylines(fp, off)) + return; + } + break; + default: + break; + } + + if (fflag) { + kq = kqueue(); + if (kq < 0) + xerr(1, "kqueue"); + action = ADD_EVENTS; + } + + for (;;) { + while ((ch = getc(fp)) != EOF) { + if (putchar(ch) == EOF) + oerr(); + } + if (ferror(fp)) { + ierr(); + return; + } + (void)fflush(stdout); + if (!fflag) + break; + + clearerr(fp); + + switch (action) { + case ADD_EVENTS: + n = 0; + + memset(ev, 0, sizeof(ev)); + if (fflag == 2 && fp != stdin) { + EV_SET(&ev[n], fileno(fp), EVFILT_VNODE, + EV_ADD | EV_ENABLE | EV_CLEAR, + NOTE_DELETE | NOTE_RENAME, 0, 0); + n++; + } + EV_SET(&ev[n], fileno(fp), EVFILT_READ, + EV_ADD | EV_ENABLE, 0, 0, 0); + n++; + + if (kevent(kq, ev, n, NULL, 0, NULL) == -1) { + close(kq); + kq = -1; + action = USE_SLEEP; + } else { + action = USE_KQUEUE; + } + break; + + case USE_KQUEUE: + if (kevent(kq, NULL, 0, ev, 1, NULL) == -1) + xerr(1, "kevent"); + + if (ev[0].filter == EVFILT_VNODE) { + /* file was rotated, wait until it reappears */ + action = USE_SLEEP; + } else if (ev[0].data < 0) { + /* file shrank, reposition to end */ + if (fseek(fp, 0L, SEEK_END) == -1) { + ierr(); + return; + } + } + break; + + case USE_SLEEP: + /* + * We pause for one second after displaying any data + * that has accumulated since we read the file. + */ + (void) sleep(1); + + if (fflag == 2 && fp != stdin && + stat(fname, &statbuf) != -1) { + if (statbuf.st_ino != sbp->st_ino || + statbuf.st_dev != sbp->st_dev || + statbuf.st_rdev != sbp->st_rdev || + statbuf.st_nlink == 0) { + fp = freopen(fname, "r", fp); + if (fp == NULL) { + ierr(); + goto out; + } + *sbp = statbuf; + if (kq != -1) + action = ADD_EVENTS; + } else if (kq != -1) + action = USE_KQUEUE; + } + break; + } + } +out: + if (fflag && kq != -1) + close(kq); +} + +/* + * rlines -- display the last offset lines of the file. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +static int +rlines(FILE *fp, off_t off, struct stat *sbp) +{ + off_t file_size; + off_t file_remaining; + char *p = NULL; + char *start = NULL; + off_t mmap_size; + off_t mmap_offset; + off_t mmap_remaining = 0; + +#define MMAP_MAXSIZE (10 * 1024 * 1024) + + if (!(file_size = sbp->st_size)) + return 0; + file_remaining = file_size; + + if (file_remaining > MMAP_MAXSIZE) { + mmap_size = MMAP_MAXSIZE; + mmap_offset = file_remaining - MMAP_MAXSIZE; + } else { + mmap_size = file_remaining; + mmap_offset = 0; + } + + while (off) { + start = mmap(NULL, (size_t)mmap_size, PROT_READ, + MAP_FILE|MAP_SHARED, fileno(fp), mmap_offset); + if (start == MAP_FAILED) { + xerr(0, "%s", fname); + return 1; + } + + mmap_remaining = mmap_size; + /* Last char is special, ignore whether newline or not. */ + for (p = start + mmap_remaining - 1 ; --mmap_remaining ; ) + if (*--p == '\n' && !--off) { + ++p; + break; + } + + file_remaining -= mmap_size - mmap_remaining; + + if (off == 0) + break; + + if (file_remaining == 0) + break; + + if (munmap(start, mmap_size)) { + xerr(0, "%s", fname); + return 1; + } + + if (mmap_offset >= MMAP_MAXSIZE) { + mmap_offset -= MMAP_MAXSIZE; + } else { + mmap_offset = 0; + mmap_size = file_remaining; + } + } + + /* + * Output the (perhaps partial) data in this mmap'd block. + */ + WR(p, mmap_size - mmap_remaining); + file_remaining += mmap_size - mmap_remaining; + if (munmap(start, mmap_size)) { + xerr(0, "%s", fname); + return 1; + } + + /* + * Set the file pointer to reflect the length displayed. + * This will cause the caller to redisplay the data if/when + * needed. + */ + if (fseeko(fp, file_remaining, SEEK_SET) == -1) { + ierr(); + return 1; + } + return 0; +} diff --git a/usr.bin/tail/misc.c b/usr.bin/tail/misc.c new file mode 100644 index 0000000..0af8f68 --- /dev/null +++ b/usr.bin/tail/misc.c @@ -0,0 +1,89 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Edward Sze-Tyan Wang. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)misc.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: misc.c,v 1.7 2011/09/03 09:02:20 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +void +ierr(void) +{ + xerr(0, "%s", fname); +} + +void +oerr(void) +{ + xerr(1, "stdout"); +} + +void +xerr(int fatal, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarn(fmt, ap); + va_end(ap); + if (fatal) + exit(1); + rval = 1; +} + +void +xerrx(int fatal, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vwarnx(fmt, ap); + va_end(ap); + if (fatal) + exit(1); + rval = 1; +} diff --git a/usr.bin/tail/read.c b/usr.bin/tail/read.c new file mode 100644 index 0000000..4e9374d --- /dev/null +++ b/usr.bin/tail/read.c @@ -0,0 +1,210 @@ +/* $NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Edward Sze-Tyan Wang. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)read.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: read.c,v 1.17 2011/09/03 10:59:10 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +/* + * displaybytes -- read bytes to an offset from the end and display. + * + * This is the function that reads to a byte offset from the end of the input, + * storing the data in a wrap-around buffer which is then displayed. If the + * rflag is set, the data is displayed in lines in reverse order, and this + * routine has the usual nastiness of trying to find the newlines. Otherwise, + * it is displayed from the character closest to the beginning of the input to + * the end. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +int +displaybytes(FILE *fp, off_t off) +{ + int ch, len, tlen; + char *ep, *p, *t; + int wrap; + char *sp; + + if ((sp = p = malloc(off)) == NULL) + xerr(1, "malloc"); + + for (wrap = 0, ep = p + off; (ch = getc(fp)) != EOF;) { + *p = ch; + if (++p == ep) { + wrap = 1; + p = sp; + } + } + if (ferror(fp)) { + ierr(); + return 1; + } + + if (rflag) { + for (t = p - 1, len = 0; t >= sp; --t, ++len) + if (*t == '\n' && len) { + WR(t + 1, len); + len = 0; + } + if (wrap) { + tlen = len; + for (t = ep - 1, len = 0; t >= p; --t, ++len) + if (*t == '\n') { + if (len) { + WR(t + 1, len); + len = 0; + } + if (tlen) { + WR(sp, tlen); + tlen = 0; + } + } + if (len) + WR(t + 1, len); + if (tlen) + WR(sp, tlen); + } + } else { + if (wrap && (len = ep - p)) + WR(p, len); + if ((len = p - sp) != 0) + WR(sp, len); + } + return 0; +} + +/* + * displaylines -- read lines to an offset from the end and display. + * + * This is the function that reads to a line offset from the end of the input, + * storing the data in an array of buffers which is then displayed. If the + * rflag is set, the data is displayed in lines in reverse order, and this + * routine has the usual nastiness of trying to find the newlines. Otherwise, + * it is displayed from the line closest to the beginning of the input to + * the end. + * + * Non-zero return means than a (non-fatal) error occurred. + */ +int +displaylines(FILE *fp, off_t off) +{ + struct { + int blen; + int len; + char *l; + } *lines; + int ch; + char *p; + int blen, cnt, recno, wrap; + char *sp, *n; + + p = NULL; + if ((lines = malloc(off * sizeof(*lines))) == NULL) + xerr(1, "malloc"); + + memset(lines, 0, sizeof(*lines) * off); + + sp = NULL; + blen = cnt = recno = wrap = 0; + + while ((ch = getc(fp)) != EOF) { + if (++cnt > blen) { + if ((n = realloc(sp, blen + 1024)) == NULL) + xerr(1, "realloc"); + sp = n; + blen += 1024; + p = sp + cnt - 1; + } + *p++ = ch; + if (ch == '\n') { + if (lines[recno].blen < cnt) { + if ((n = realloc(lines[recno].l, + cnt + 256)) == NULL) + xerr(1, "realloc"); + lines[recno].l = n; + lines[recno].blen = cnt + 256; + } + memmove(lines[recno].l, sp, lines[recno].len = cnt); + cnt = 0; + p = sp; + if (++recno == off) { + wrap = 1; + recno = 0; + } + } + } + if (ferror(fp)) { + free(lines); + ierr(); + return 1; + } + if (cnt) { + lines[recno].l = sp; + lines[recno].len = cnt; + if (++recno == off) { + wrap = 1; + recno = 0; + } + } + + if (rflag) { + for (cnt = recno - 1; cnt >= 0; --cnt) + WR(lines[cnt].l, lines[cnt].len); + if (wrap) + for (cnt = off - 1; cnt >= recno; --cnt) + WR(lines[cnt].l, lines[cnt].len); + } else { + if (wrap) + for (cnt = recno; cnt < off; ++cnt) + WR(lines[cnt].l, lines[cnt].len); + for (cnt = 0; cnt < recno; ++cnt) + WR(lines[cnt].l, lines[cnt].len); + } + free(lines); + return 0; +} diff --git a/usr.bin/tail/reverse.c b/usr.bin/tail/reverse.c new file mode 100644 index 0000000..bb8bcd9 --- /dev/null +++ b/usr.bin/tail/reverse.c @@ -0,0 +1,269 @@ +/* $NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Edward Sze-Tyan Wang. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)reverse.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: reverse.c,v 1.23 2011/09/03 10:59:11 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include "extern.h" + +static void r_buf(FILE *); +static void r_reg(FILE *, enum STYLE, off_t, struct stat *); + +/* + * reverse -- display input in reverse order by line. + * + * There are six separate cases for this -- regular and non-regular + * files by bytes, lines or the whole file. + * + * BYTES display N bytes + * REG mmap the file and display the lines + * NOREG cyclically read characters into a wrap-around buffer + * + * LINES display N lines + * REG mmap the file and display the lines + * NOREG cyclically read lines into a wrap-around array of buffers + * + * FILE display the entire file + * REG mmap the file and display the lines + * NOREG cyclically read input into a linked list of buffers + */ +void +reverse(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ + if (style != REVERSE && off == 0) + return; + + if (S_ISREG(sbp->st_mode)) + r_reg(fp, style, off, sbp); + else + switch(style) { + case FBYTES: + case RBYTES: + (void)displaybytes(fp, off); + break; + case FLINES: + case RLINES: + (void)displaylines(fp, off); + break; + case REVERSE: + r_buf(fp); + break; + default: + break; + } +} + +/* + * r_reg -- display a regular file in reverse order by line. + */ +static void +r_reg(FILE *fp, enum STYLE style, off_t off, struct stat *sbp) +{ + off_t size; + int llen; + char *p; + char *start; + + if (!(size = sbp->st_size)) + return; + + if ((uint64_t)size > SIZE_T_MAX) { + /* XXX: need a cleaner way to check this on amd64 */ + errno = EFBIG; + xerr(0, "%s", fname); + return; + } + + if ((start = mmap(NULL, (size_t)size, PROT_READ, + MAP_FILE|MAP_SHARED, fileno(fp), (off_t)0)) == MAP_FAILED) { + xerr(0, "%s", fname); + return; + } + p = start + size - 1; + + if (style == RBYTES && off < size) + size = off; + + /* Last char is special, ignore whether newline or not. */ + for (llen = 1; --size; ++llen) + if (*--p == '\n') { + WR(p + 1, llen); + llen = 0; + if (style == RLINES && !--off) { + ++p; + break; + } + } + if (llen) + WR(p, llen); + if (munmap(start, (size_t)sbp->st_size)) + xerr(0, "%s", fname); +} + +typedef struct bf { + struct bf *next; + struct bf *prev; + int len; + char *l; +} BF; + +/* + * r_buf -- display a non-regular file in reverse order by line. + * + * This is the function that saves the entire input, storing the data in a + * doubly linked list of buffers and then displays them in reverse order. + * It has the usual nastiness of trying to find the newlines, as there's no + * guarantee that a newline occurs anywhere in the file, let alone in any + * particular buffer. If we run out of memory, input is discarded (and the + * user warned). + */ +static void +r_buf(FILE *fp) +{ + BF *mark, *tl, *tr; + int ch, len, llen; + char *p; + off_t enomem; + +#define BSZ (128 * 1024) + tl = NULL; + for (mark = NULL, enomem = 0;;) { + /* + * Allocate a new block and link it into place in a doubly + * linked list. If out of memory, toss the LRU block and + * keep going. + */ + if (enomem) { + if (!mark) { + errno = ENOMEM; + xerr(1, NULL); + } + tl = tl->next; + enomem += tl->len; + } else if ((tl = malloc(sizeof(*tl))) == NULL || + (tl->l = malloc(BSZ)) == NULL) { + if (tl) + free(tl); + if (!mark) { + errno = ENOMEM; + xerr(1, NULL); + } + tl = mark; + enomem += tl->len; + } else if (mark) { + tl->next = mark; + tl->prev = mark->prev; + mark->prev->next = tl; + mark->prev = tl; + } else { + mark = tl; + mark->next = mark->prev = mark; + } + + /* Fill the block with input data. */ + ch = 0; + for (p = tl->l, len = 0; + len < BSZ && (ch = getc(fp)) != EOF; ++len) + *p++ = ch; + + /* + * If no input data for this block and we tossed some data, + * recover it. + */ + if (!len) { + if (enomem) + enomem -= tl->len; + tl = tl->prev; + break; + } + + tl->len = len; + if (ch == EOF) + break; + } + + if (enomem) { + xerrx(0, "Warning: %lld bytes discarded", (long long)enomem); + } + + /* + * Step through the blocks in the reverse order read. The last char + * is special, ignore whether newline or not. + */ + for (mark = tl;;) { + for (p = tl->l + (len = tl->len) - 1, llen = 0; len--; + --p, ++llen) + if (*p == '\n') { + if (llen) { + WR(p + 1, llen); + llen = 0; + } + if (tl == mark) + continue; + for (tr = tl->next; tr->len; tr = tr->next) { + WR(tr->l, tr->len); + tr->len = 0; + if (tr == mark) + break; + } + } + tl->len = llen; + if ((tl = tl->prev) == mark) + break; + } + tl = tl->next; + if (tl->len) { + WR(tl->l, tl->len); + tl->len = 0; + } + while ((tl = tl->next)->len) { + WR(tl->l, tl->len); + tl->len = 0; + } +} diff --git a/usr.bin/tail/tac.1 b/usr.bin/tail/tac.1 new file mode 100644 index 0000000..d0dd1e8 --- /dev/null +++ b/usr.bin/tail/tac.1 @@ -0,0 +1,56 @@ +.\" $NetBSD: tac.1,v 1.2 2017/10/01 22:35:23 kre Exp $ +.\" +.\" Copyright (c) 2017 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Maya Rashish. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +.\" LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +.\" FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +.\" COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +.\" INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING, +.\" BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +.\" LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +.\" AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +.\" OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +.\" OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.Dd October 1, 2017 +.Dt TAC 1 +.Os +.Sh NAME +.Nm tac +.Nd display file in reverse +.Sh SYNOPSIS +.Nm +.Op Ar file ... +.Sh DESCRIPTION +.Nm +.Pq cat backwards +outputs the contents of each of each of the specified files, +or the standard input if no files are specified, +in reverse line order to the standard output. +.Sh EXIT STATUS +.Ex -std tac +.Sh COMPATIBILITY +The +.Nm +utility doesn't support any of the options of GNU tac. +.Sh SEE ALSO +.Xr cat 1 , +.Xr tail 1 diff --git a/usr.bin/tail/tail.1 b/usr.bin/tail/tail.1 new file mode 100644 index 0000000..1a68f88 --- /dev/null +++ b/usr.bin/tail/tail.1 @@ -0,0 +1,202 @@ +.\" $NetBSD: tail.1,v 1.20 2017/10/15 03:57:47 pgoyette Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tail.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd October 1, 2017 +.Dt TAIL 1 +.Os +.Sh NAME +.Nm tail +.Nd display the last part of a file +.Sh SYNOPSIS +.Nm +.Fl qv +.Oo +.Fl f | +.Fl F | +.Fl r +.Oc +.Oo +.Fl b Ar number | +.Fl c Ar number | +.Fl n Ar number +.Oc +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility displays the contents of +.Ar file +or, by default, its standard input, to the standard output. +.Pp +The display begins at a byte, line or 512-byte block location in the +input. +Numbers having a leading plus (``+'') sign are relative to the beginning +of the input, for example, +.Dq -c +2 +starts the display at the second +byte of the input. +Numbers having a leading minus (``-'') sign or no explicit sign are +relative to the end of the input, for example, +.Dq -n 2 +displays the last two lines of the input. +The default starting location is +.Dq -n 10 , +or the last 10 lines of the input. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl b Ar number +The location is +.Ar number +512-byte blocks. +.It Fl c Ar number +The location is +.Ar number +bytes. +.It Fl f +The +.Fl f +option causes +.Nm +not to stop when end of file is reached, but rather to wait for additional +data to be appended to the input. +The +.Fl f +option is ignored if there are no file arguments and the standard +input is a pipe or a FIFO. +.It Fl F +The +.Fl F +option is the same as the +.Fl f +option, except that every five seconds +.Nm +will check to see if the file named on the command line has been +shortened or moved (it is considered moved if the inode or device +number changes) and, if so, it will close +the current file, open the filename given, print out the entire +contents, and continue to wait for more data to be appended. +This option is used to follow log files though rotation by +.Xr newsyslog 8 +or similar programs. +.It Fl n Ar number +The location is +.Ar number +lines. +.It Fl q +Do not prepend a header for each file, even if multiple files +are specified. +.It Fl r +The +.Fl r +option causes the input to be displayed in reverse order, by line. +Additionally, this option changes the meaning of the +.Fl b , +.Fl c +and +.Fl n +options. +When the +.Fl r +option is specified, these options specify the number of bytes, lines +or 512-byte blocks to display, instead of the bytes, lines or blocks +from the beginning or end of the input from which to begin the display. +The default for the +.Fl r +option is to display all of the input. +.It Fl v +Prepend each file with a header. +.El +.Pp +If more than a single file is specified, or the +.Fl v +option is used, each file is preceded by a +header consisting of the string +.Dq ==> XXX \*[Le]= +where +.Dq XXX +is the name of the file. +The +.Fl q +flag disables the printing of the header in all cases. +.Sh EXIT STATUS +.Ex -std tail +.Sh SEE ALSO +.Xr cat 1 , +.Xr head 1 , +.Xr sed 1 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2-92 +specification. +In particular, the +.Fl b , +.Fl r +and +.Fl F +options are extensions to that standard. +.Pp +The historic command line syntax of +.Nm +is supported by this implementation. +The only difference between this implementation and historic versions +of +.Nm , +once the command line syntax translation has been done, is that the +.Fl b , +.Fl c +and +.Fl n +options modify the +.Fl r +option, i.e., ``-r -c 4'' displays the last 4 characters of the last line +of the input, while the historic tail (using the historic syntax ``-4cr'') +would ignore the +.Fl c +option and display the last 4 lines of the input. +.Sh HISTORY +A +.Nm +command appeared in +.At v7 . +.Sh BUGS +When using the +.Fl F +option, +.Nm +will not detect a file truncation if, between the truncation +and the next check of the file size, data written to the file make +it larger than the last known file size. diff --git a/usr.bin/tail/tail.c b/usr.bin/tail/tail.c new file mode 100644 index 0000000..dff9559 --- /dev/null +++ b/usr.bin/tail/tail.c @@ -0,0 +1,323 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Edward Sze-Tyan Wang. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tail.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: tail.c,v 1.20 2018/03/06 03:33:26 eadler Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include "extern.h" + +int fflag, rflag, rval; +const char *fname; + +static void obsolete(char **); +static void usage(void) __dead; + +int +main(int argc, char *argv[]) +{ + struct stat sb; + FILE *fp; + off_t off; + enum STYLE style; + int ch, first; + char *p; + int qflag = 0; + int vflag = 0; + + setprogname(argv[0]); + off = 0; + /* + * Tail's options are weird. First, -n10 is the same as -n-10, not + * -n+10. Second, the number options are 1 based and not offsets, + * so -n+1 is the first line, and -c-1 is the last byte. Third, the + * number options for the -r option specify the number of things that + * get displayed, not the starting point in the file. The one major + * incompatibility in this version as compared to historical versions + * is that the 'r' option couldn't be modified by the -lbc options, + * i.e., it was always done in lines. This version treats -rc as a + * number of characters in reverse order. Finally, the default for + * -r is the entire file, not 10 lines. + */ +#define ARG(units, forward, backward) { \ + if (style) \ + usage(); \ + off = strtoll(optarg, &p, 10) * (units); \ + if (*p) \ + xerrx(1, "illegal offset -- %s", optarg); \ + switch(optarg[0]) { \ + case '+': \ + if (off) \ + off -= (units); \ + style = (forward); \ + break; \ + case '-': \ + off = -off; \ + /* FALLTHROUGH */ \ + default: \ + style = (backward); \ + break; \ + } \ +} + + obsolete(argv); + style = NOTSET; + if (strcmp(getprogname(), "tac") == 0) { + qflag = 1; + vflag = 0; + rflag = 1; + argc -= 1; + argv += 1; + } else { /* tail */ + while ((ch = getopt(argc, argv, "Fb:c:fn:rqv")) != -1) + switch(ch) { + case 'F': + fflag = 2; + break; + case 'b': + ARG(512, FBYTES, RBYTES); + break; + case 'c': + ARG(1, FBYTES, RBYTES); + break; + case 'f': + fflag = 1; + break; + case 'n': + ARG(1, FLINES, RLINES); + break; + case 'r': + rflag = 1; + break; + case 'q': + qflag = 1; + vflag = 0; + break; + case 'v': + qflag = 0; + vflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + } + + if (fflag && argc > 1) + xerrx(1, + "-f and -F options only appropriate for a single file"); + + /* + * If displaying in reverse, don't permit follow option, and convert + * style values. + */ + if (rflag) { + if (fflag) + usage(); + if (style == FBYTES) + style = RBYTES; + else if (style == FLINES) + style = RLINES; + } + + /* + * If style not specified, the default is the whole file for -r, and + * the last 10 lines if not -r. + */ + if (style == NOTSET) { + if (rflag) { + off = 0; + style = REVERSE; + } else { + off = 10; + style = RLINES; + } + } + if (*argv) + for (first = 1; (fname = *argv++) != NULL;) { + if ((fp = fopen(fname, "r")) == NULL || + fstat(fileno(fp), &sb)) { + ierr(); + continue; + } + if (vflag || (qflag == 0 && argc > 1)) { + (void)printf("%s==> %s <==\n", + first ? "" : "\n", fname); + first = 0; + (void)fflush(stdout); + } + + if (rflag) + reverse(fp, style, off, &sb); + else + forward(fp, style, off, &sb); + (void)fclose(fp); + } + else { + fname = "stdin"; + + if (fstat(fileno(stdin), &sb)) { + ierr(); + exit(1); + } + + /* + * Determine if input is a pipe. 4.4BSD will set the SOCKET + * bit in the st_mode field for pipes. Fix this then. + */ + if (lseek(fileno(stdin), (off_t)0, SEEK_CUR) == -1 && + errno == ESPIPE) { + errno = 0; + fflag = 0; /* POSIX.2 requires this. */ + } + + if (rflag) + reverse(stdin, style, off, &sb); + else + forward(stdin, style, off, &sb); + } + exit(rval); +} + +/* + * Convert the obsolete argument form into something that getopt can handle. + * This means that anything of the form [+-][0-9][0-9]*[lbc][fr] that isn't + * the option argument for a -b, -c or -n option gets converted. + */ +static void +obsolete(char *argv[]) +{ + char *ap, *p, *t; + size_t len; + char *start; + + while ((ap = *++argv) != NULL) { + /* Return if "--" or not an option of any form. */ + if (ap[0] != '-') { + if (ap[0] != '+') + return; + } else if (ap[1] == '-') + return; + + switch (*++ap) { + /* Old-style option. */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + + /* Malloc space for dash, new option and argument. */ + len = strlen(*argv); + if ((start = p = malloc(len + 3)) == NULL) + xerr(1, "malloc"); + *p++ = '-'; + + /* + * Go to the end of the option argument. Save off any + * trailing options (-3lf) and translate any trailing + * output style characters. + */ + t = *argv + len - 1; + if (*t == 'f' || *t == 'r') { + *p++ = *t; + *t-- = '\0'; + } + switch(*t) { + case 'b': + *p++ = 'b'; + *t = '\0'; + break; + case 'c': + *p++ = 'c'; + *t = '\0'; + break; + case 'l': + *t = '\0'; + /* FALLTHROUGH */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + *p++ = 'n'; + break; + default: + xerrx(1, "illegal option -- %s", *argv); + } + *p++ = *argv[0]; + (void)strcpy(p, ap); + *argv = start; + continue; + + /* + * Options w/ arguments, skip the argument and continue + * with the next option. + */ + case 'b': + case 'c': + case 'n': + if (!ap[1]) + ++argv; + /* FALLTHROUGH */ + /* Options w/o arguments, continue with the next option. */ + case 'f': + case 'r': + continue; + + /* Illegal option, return and let getopt handle it. */ + default: + return; + } + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-qv] [-f | -F | -r] [-b # | -c # | -n #] [file ...]\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/talk/ctl.c b/usr.bin/talk/ctl.c new file mode 100644 index 0000000..ef6f924 --- /dev/null +++ b/usr.bin/talk/ctl.c @@ -0,0 +1,125 @@ +/* $NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ctl.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: ctl.c,v 1.10 2012/03/20 20:34:59 matt Exp $"); +#endif /* not lint */ + +/* + * This file handles haggling with the various talk daemons to + * get a socket to talk to. sockt is opened and connected in + * the progress + */ + +#include "talk.h" +#include +#include "talk_ctl.h" + +struct sockaddr_in daemon_addr = { sizeof(daemon_addr), AF_INET, 0, {0}, {0} }; +struct sockaddr_in ctl_addr = { sizeof(ctl_addr), AF_INET, 0, {0}, {0} }; +struct sockaddr_in my_addr = { sizeof(my_addr), AF_INET, 0, {0}, {0} }; + + /* inet addresses of the two machines */ +struct in_addr my_machine_addr; +struct in_addr his_machine_addr; + +u_short daemon_port; /* port number of the talk daemon */ + +int ctl_sockt; +int sockt; +int invitation_waiting = 0; + +CTL_MSG msg; + +void +open_sockt(void) +{ + socklen_t length; + + + (void)memset(&my_addr, 0, sizeof(my_addr)); + my_addr.sin_family = AF_INET; +#ifdef BSD4_4 + my_addr.sin_len = sizeof(my_addr); +#endif + my_addr.sin_addr = my_machine_addr; + my_addr.sin_port = 0; + sockt = socket(AF_INET, SOCK_STREAM, 0); + if (sockt == -1) + p_error("Bad socket"); + if (bind(sockt, (struct sockaddr *)&my_addr, sizeof(my_addr)) != 0) + p_error("Binding local socket"); + length = sizeof(my_addr); + if (getsockname(sockt, (struct sockaddr *)&my_addr, &length) == -1) + p_error("Bad address for socket"); +} + +/* open the ctl socket */ +void +open_ctl(void) +{ + socklen_t length; + + (void)memset(&ctl_addr, 0, sizeof(ctl_addr)); + ctl_addr.sin_family = AF_INET; +#ifdef BSD4_4 + ctl_addr.sin_len = sizeof(my_addr); +#endif + ctl_addr.sin_port = 0; + ctl_addr.sin_addr = my_machine_addr; + ctl_sockt = socket(AF_INET, SOCK_DGRAM, 0); + if (ctl_sockt == -1) + p_error("Bad socket"); + if (bind(ctl_sockt, + (struct sockaddr *)&ctl_addr, sizeof(ctl_addr)) != 0) + p_error("Couldn't bind to control socket"); + length = sizeof(ctl_addr); + if (getsockname(ctl_sockt, + (struct sockaddr *)&ctl_addr, &length) == -1) + p_error("Bad address for ctl socket"); +} + +/* print_addr is a debug print routine */ +void +print_addr(struct sockaddr_in addr) +{ + int i; + + printf("addr = %s, port = %o, family = %o zero = ", + inet_ntoa(addr.sin_addr), addr.sin_port, addr.sin_family); + for (i = 0; i<8;i++) + printf("%o ", (int)addr.sin_zero[i]); + putchar('\n'); +} diff --git a/usr.bin/talk/ctl_transact.c b/usr.bin/talk/ctl_transact.c new file mode 100644 index 0000000..4d0613f --- /dev/null +++ b/usr.bin/talk/ctl_transact.c @@ -0,0 +1,108 @@ +/* $NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)ctl_transact.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: ctl_transact.c,v 1.11 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include +#include +#include +#include +#include "talk_ctl.h" + +#define CTL_WAIT 2 /* time to wait for a response, in seconds */ + +/* + * SOCKDGRAM is unreliable, so we must repeat messages if we have + * not received an acknowledgement within a reasonable amount + * of time + */ +void +ctl_transact(struct in_addr target, CTL_MSG tmsg, int type, CTL_RESPONSE *rp) +{ + struct pollfd set[1]; + int nready, cc; + + nready = 0; + tmsg.type = type; + daemon_addr.sin_addr = target; + daemon_addr.sin_port = daemon_port; + set[0].fd = ctl_sockt; + set[0].events = POLLIN; + + /* + * Keep sending the message until a response of + * the proper type is obtained. + */ + do { + /* resend message until a response is obtained */ + do { + cc = sendto(ctl_sockt, (char *)&tmsg, sizeof (tmsg), 0, + (struct sockaddr *)&daemon_addr, + sizeof (daemon_addr)); + if (cc != sizeof (tmsg)) { + if (errno == EINTR) + continue; + p_error("Error on write to talk daemon"); + } + nready = poll(set, 1, CTL_WAIT * 1000); + if (nready < 0) { + if (errno == EINTR) + continue; + p_error("Error waiting for daemon response"); + } + } while (nready == 0); + /* + * Keep reading while there are queued messages + * (this is not necessary, it just saves extra + * request/acknowledgements being sent) + */ + do { + cc = recv(ctl_sockt, (char *)rp, sizeof (*rp), 0); + if (cc < 0) { + if (errno == EINTR) + continue; + p_error("Error on read from talk daemon"); + } + /* an immediate poll */ + nready = poll(set, 1, 0); + } while (nready > 0 && (rp->vers != TALK_VERSION || + rp->type != type)); + } while (rp->vers != TALK_VERSION || rp->type != type); + rp->id_num = ntohl(rp->id_num); + rp->addr.sa_family = ntohs(rp->addr.sa_family); +} diff --git a/usr.bin/talk/display.c b/usr.bin/talk/display.c new file mode 100644 index 0000000..7017f3f --- /dev/null +++ b/usr.bin/talk/display.c @@ -0,0 +1,195 @@ +/* $NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)display.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: display.c,v 1.9 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +/* + * The window 'manager', initializes curses and handles the actual + * displaying of text + */ +#include "talk.h" +#include + +xwin_t my_win; +xwin_t his_win; +WINDOW *line_win; + +int curses_initialized = 0; + +/* + * max HAS to be a function, it is called with + * a argument of the form --foo at least once. + */ +int +max(int a, int b) +{ + + return (a > b ? a : b); +} + +/* + * Display some text on somebody's window, processing some control + * characters while we are at it. + */ +void +display(xwin_t *win, char *text, int size) +{ + int i; + char cch; + + for (i = 0; i < size; i++) { + if (*text == '\n') { + xscroll(win, 0); + text++; + continue; + } + /* erase character */ + if (*text == win->cerase) { + wmove(win->x_win, win->x_line, max(--win->x_col, 0)); + getyx(win->x_win, win->x_line, win->x_col); + waddch(win->x_win, ' '); + wmove(win->x_win, win->x_line, win->x_col); + getyx(win->x_win, win->x_line, win->x_col); + text++; + continue; + } + /* + * On word erase search backwards until we find + * the beginning of a word or the beginning of + * the line. + */ + if (*text == win->werase) { + int endcol, xcol, j, c; + + endcol = win->x_col; + xcol = endcol - 1; + while (xcol >= 0) { + c = readwin(win->x_win, win->x_line, xcol); + if (c != ' ') + break; + xcol--; + } + while (xcol >= 0) { + c = readwin(win->x_win, win->x_line, xcol); + if (c == ' ') + break; + xcol--; + } + wmove(win->x_win, win->x_line, xcol + 1); + for (j = xcol + 1; j < endcol; j++) + waddch(win->x_win, ' '); + wmove(win->x_win, win->x_line, xcol + 1); + getyx(win->x_win, win->x_line, win->x_col); + text++; + continue; + } + /* line kill */ + if (*text == win->kill) { + wmove(win->x_win, win->x_line, 0); + wclrtoeol(win->x_win); + getyx(win->x_win, win->x_line, win->x_col); + text++; + continue; + } + if (*text == '\f') { + if (win == &my_win) + wrefresh(curscr); + text++; + continue; + } + if (*text == '\07') { + beep(); + text++; + continue; + } + if (win->x_col == COLS-1) { + /* check for wraparound */ + xscroll(win, 0); + } + if ( !isprint((u_char)*text) && *text != '\t') { + waddch(win->x_win, '^'); + getyx(win->x_win, win->x_line, win->x_col); + if (win->x_col == COLS-1) /* check for wraparound */ + xscroll(win, 0); + cch = (*text & 63) + 64; + waddch(win->x_win, cch); + } else + waddch(win->x_win, *text); + getyx(win->x_win, win->x_line, win->x_col); + text++; + } + wrefresh(win->x_win); +} + +/* + * Read the character at the indicated position in win + */ +int +readwin(WINDOW *win, int line, int col) +{ + int oldline, oldcol; + int c; + + getyx(win, oldline, oldcol); + wmove(win, line, col); + c = winch(win); + wmove(win, oldline, oldcol); + return (c); +} + +/* + * Scroll a window, blanking out the line following the current line + * so that the current position is obvious + */ +void +xscroll(xwin_t *win, int flag) +{ + + if (flag == -1) { + wmove(win->x_win, 0, 0); + win->x_line = 0; + win->x_col = 0; + return; + } + win->x_line = (win->x_line + 1) % win->x_nlines; + win->x_col = 0; + wmove(win->x_win, win->x_line, win->x_col); + wclrtoeol(win->x_win); + wmove(win->x_win, (win->x_line + 1) % win->x_nlines, win->x_col); + wclrtoeol(win->x_win); + wmove(win->x_win, win->x_line, win->x_col); +} diff --git a/usr.bin/talk/get_addrs.c b/usr.bin/talk/get_addrs.c new file mode 100644 index 0000000..f381cc7 --- /dev/null +++ b/usr.bin/talk/get_addrs.c @@ -0,0 +1,81 @@ +/* $NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)get_addrs.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: get_addrs.c,v 1.12 2015/06/16 22:54:11 christos Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include +#include +#include +#include +#include +#include "talk_ctl.h" + +void +get_addrs(const char *my_machine_name, const char *his_machine_name) +{ + struct hostent *hp; + struct servent *sp; + + msg.pid = htonl(getpid()); + /* + * If the callee is on-machine, just use loopback + * otherwise do a lookup... + */ + if (strcmp(his_machine_name, my_machine_name) != 0) { + /* look up the address of the local host */ + hp = gethostbyname(my_machine_name); + if (hp == NULL) + errx(EXIT_FAILURE, "%s: %s", my_machine_name, + hstrerror(h_errno)); + memcpy(&my_machine_addr, hp->h_addr, sizeof(my_machine_addr)); + hp = gethostbyname(his_machine_name); + if (hp == NULL) + errx(EXIT_FAILURE, "%s: %s", his_machine_name, + hstrerror(h_errno)); + memcpy(&his_machine_addr, hp->h_addr, sizeof(his_machine_addr)); + } else + his_machine_addr.s_addr = my_machine_addr.s_addr = + htonl(INADDR_LOOPBACK); + + /* find the server's port */ + sp = getservbyname("ntalk", "udp"); + if (sp == 0) + errx(EXIT_FAILURE, "%s/%s: service is not registered.", + "ntalk", "udp"); + daemon_port = sp->s_port; +} diff --git a/usr.bin/talk/get_names.c b/usr.bin/talk/get_names.c new file mode 100644 index 0000000..5400548 --- /dev/null +++ b/usr.bin/talk/get_names.c @@ -0,0 +1,124 @@ +/* $NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)get_names.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: get_names.c,v 1.16 2012/12/29 23:44:23 christos Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include +#include +#include +#include +#include +#include + +extern CTL_MSG msg; + +/* + * Determine the local and remote user, tty, and machines + */ +void +get_names(int argc, char *argv[]) +{ + char hostname[MAXHOSTNAMELEN + 1]; + const char *his_name, *my_name; + const char *my_machine_name, *his_machine_name; + const char *his_tty; + char *cp; + char *names; + + if (argc < 2 ) { + fprintf(stderr, "Usage: %s user [ttyname]\n", getprogname()); + exit(1); + } + if (!isatty(0)) + errx(EXIT_FAILURE, "Standard input must be a tty, " + "not a pipe or a file"); + if ((my_name = getlogin()) == NULL) { + struct passwd *pw; + + if ((pw = getpwuid(getuid())) == NULL) + errx(EXIT_FAILURE, "You don't exist. Go away."); + my_name = pw->pw_name; + } + if ((cp = getenv("TALKHOST")) != NULL) + (void)estrlcpy(hostname, cp, sizeof(hostname)); + else { + if (gethostname(hostname, sizeof(hostname)) == -1) + err(EXIT_FAILURE, "gethostname"); + hostname[sizeof(hostname) - 1] = '\0'; + } + my_machine_name = hostname; + /* check for, and strip out, the machine name of the target */ + names = strdup(argv[1]); + for (cp = names; *cp && !strchr("@:!.", *cp); cp++) + ; + if (*cp == '\0') { + /* this is a local to local talk */ + his_name = names; + his_machine_name = my_machine_name; + } else { + if (*cp++ == '@') { + /* user@host */ + his_name = names; + his_machine_name = cp; + } else { + /* host.user or host!user or host:user */ + his_name = cp; + his_machine_name = names; + } + *--cp = '\0'; + } + if (argc > 2) + his_tty = argv[2]; /* tty name is arg 2 */ + else + his_tty = ""; + get_addrs(my_machine_name, his_machine_name); + /* + * Initialize the message template. + */ + msg.vers = TALK_VERSION; + msg.addr.sa_family = htons(AF_INET); + msg.ctl_addr.sa_family = htons(AF_INET); + msg.id_num = htonl(0); + strncpy(msg.l_name, my_name, NAME_SIZE); + msg.l_name[NAME_SIZE - 1] = '\0'; + strncpy(msg.r_name, his_name, NAME_SIZE); + msg.r_name[NAME_SIZE - 1] = '\0'; + strncpy(msg.r_tty, his_tty, TTY_SIZE); + msg.r_tty[TTY_SIZE - 1] = '\0'; + free(names); +} diff --git a/usr.bin/talk/init_disp.c b/usr.bin/talk/init_disp.c new file mode 100644 index 0000000..a006705 --- /dev/null +++ b/usr.bin/talk/init_disp.c @@ -0,0 +1,153 @@ +/* $NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)init_disp.c 8.2 (Berkeley) 2/16/94"; +#endif +__RCSID("$NetBSD: init_disp.c,v 1.12 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +/* + * Initialization code for the display package, + * as well as the signal handling routines. + */ + +#include "talk.h" +#include +#include +#include +#include +#include +#include +#include + +/* + * Set up curses, catch the appropriate signals, + * and build the various windows. + */ +void +init_display(void) +{ + struct sigaction sa; + + if (initscr() == NULL) + errx(1, "Terminal type unset or lacking necessary features."); + (void)sigaction(SIGTSTP, NULL, &sa); + sigaddset(&sa.sa_mask, SIGALRM); + (void)sigaction(SIGTSTP, &sa, NULL); + curses_initialized = 1; + clear(); + refresh(); + noecho(); + cbreak(); + signal(SIGINT, sig_sent); + signal(SIGPIPE, sig_sent); + /* curses takes care of ^Z */ + my_win.x_nlines = LINES / 2; + my_win.x_ncols = COLS; + my_win.x_win = newwin(my_win.x_nlines, my_win.x_ncols, 0, 0); + scrollok(my_win.x_win, FALSE); + wclear(my_win.x_win); + + his_win.x_nlines = LINES / 2 - 1; + his_win.x_ncols = COLS; + his_win.x_win = newwin(his_win.x_nlines, his_win.x_ncols, + my_win.x_nlines+1, 0); + scrollok(his_win.x_win, FALSE); + wclear(his_win.x_win); + + line_win = newwin(1, COLS, my_win.x_nlines, 0); + box(line_win, '-', '-'); + wrefresh(line_win); + /* let them know we are working on it */ + current_state = "No connection yet"; +} + +/* + * Trade edit characters with the other talk. By agreement + * the first three characters each talk transmits after + * connection are the three edit characters. + */ +void +set_edit_chars(void) +{ + char buf[3]; + int cc; + struct termios tty; + + tcgetattr(0, &tty); + my_win.cerase = tty.c_cc[VERASE]; + my_win.kill = tty.c_cc[VKILL]; + if (tty.c_cc[VWERASE] == (unsigned char) -1) + my_win.werase = '\027'; /* control W */ + else + my_win.werase = tty.c_cc[VWERASE]; + buf[0] = my_win.cerase; + buf[1] = my_win.kill; + buf[2] = my_win.werase; + cc = write(sockt, buf, sizeof(buf)); + if (cc != sizeof(buf) ) + p_error("Lost the connection"); + cc = read(sockt, buf, sizeof(buf)); + if (cc != sizeof(buf) ) + p_error("Lost the connection"); + his_win.cerase = buf[0]; + his_win.kill = buf[1]; + his_win.werase = buf[2]; +} + +void +sig_sent(int dummy) +{ + + message("Connection closing. Exiting"); + quit(); +} + +/* + * All done talking...hang up the phone and reset terminal thingy's + */ +void +quit(void) +{ + + if (curses_initialized) { + wmove(his_win.x_win, his_win.x_nlines-1, 0); + wclrtoeol(his_win.x_win); + wrefresh(his_win.x_win); + endwin(); + } + if (invitation_waiting) + send_delete(); + exit(0); +} diff --git a/usr.bin/talk/invite.c b/usr.bin/talk/invite.c new file mode 100644 index 0000000..680744c --- /dev/null +++ b/usr.bin/talk/invite.c @@ -0,0 +1,190 @@ +/* $NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)invite.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: invite.c,v 1.10 2012/12/29 23:44:23 christos Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include +#include +#include +#include +#include +#include +#include "talk_ctl.h" + +/* + * There wasn't an invitation waiting, so send a request containing + * our sockt address to the remote talk daemon so it can invite + * him + */ + +/* + * The msg.id's for the invitations + * on the local and remote machines. + * These are used to delete the + * invitations. + */ +static int local_id, remote_id; +static jmp_buf invitebuf; + +void +invite_remote(void) +{ + int new_sockt; + struct itimerval itimer; + CTL_RESPONSE response; + + itimer.it_value.tv_sec = RING_WAIT; + itimer.it_value.tv_usec = 0; + itimer.it_interval = itimer.it_value; + if (listen(sockt, 5) != 0) + p_error("Error on attempt to listen for caller"); +#ifdef MSG_EOR + /* copy new style sockaddr to old, swap family (short in old) */ + msg.addr = *(struct talkd_sockaddr *)(void *)&my_addr; + msg.addr.sa_family = htons(my_addr.sin_family); +#else + msg.addr = *(struct sockaddr *)&my_addr; +#endif + msg.id_num = htonl(-1); /* an impossible id_num */ + invitation_waiting = 1; + announce_invite(); + /* + * Shut off the automatic messages for a while, + * so we can use the interrupt timer to resend the invitation + */ + end_msgs(); + setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0); + message("Waiting for your party to respond"); + signal(SIGALRM, re_invite); + (void) setjmp(invitebuf); + while ((new_sockt = accept(sockt, 0, 0)) < 0) { + if (errno == EINTR) + continue; + p_error("Unable to connect with your party"); + } + close(sockt); + sockt = new_sockt; + + /* + * Have the daemons delete the invitations now that we + * have connected. + */ + current_state = "Waiting for your party to respond"; + start_msgs(); + + msg.id_num = htonl(local_id); + ctl_transact(my_machine_addr, msg, DELETE, &response); + msg.id_num = htonl(remote_id); + ctl_transact(his_machine_addr, msg, DELETE, &response); + invitation_waiting = 0; +} + +/* + * Routine called on interrupt to re-invite the callee + */ +void +re_invite(int dummy) +{ + + message("Ringing your party again"); + current_line++; + /* force a re-announce */ + msg.id_num = htonl(remote_id + 1); + announce_invite(); + longjmp(invitebuf, 1); +} + +static const char *answers[] = { + "answer #0", /* SUCCESS */ + "Your party is not logged on", /* NOT_HERE */ + "Target machine is too confused to talk to us", /* FAILED */ + "Target machine does not recognize us", /* MACHINE_UNKNOWN */ + "Your party is refusing messages", /* PERMISSION_REFUSED */ + "Target machine can not handle remote talk", /* UNKNOWN_REQUEST */ + "Target machine indicates protocol mismatch", /* BADVERSION */ + "Target machine indicates protocol botch (addr)",/* BADADDR */ + "Target machine indicates protocol botch (ctl_addr)",/* BADCTLADDR */ +}; +#define NANSWERS (sizeof (answers) / sizeof (answers[0])) + +/* + * Transmit the invitation and process the response + */ +void +announce_invite(void) +{ + CTL_RESPONSE response; + + current_state = "Trying to connect to your party's talk daemon"; + ctl_transact(his_machine_addr, msg, ANNOUNCE, &response); + remote_id = response.id_num; + if (response.answer != SUCCESS) { + if (response.answer < NANSWERS) + message(answers[response.answer]); + quit(); + } + /* leave the actual invitation on my talk daemon */ + ctl_transact(my_machine_addr, msg, LEAVE_INVITE, &response); + local_id = response.id_num; +} + +/* + * Tell the daemon to remove your invitation + */ +void +send_delete(void) +{ + + msg.type = DELETE; + /* + * This is just a extra clean up, so just send it + * and don't wait for an answer + */ + msg.id_num = htonl(remote_id); + daemon_addr.sin_addr = his_machine_addr; + if (sendto(ctl_sockt, &msg, sizeof (msg), 0, + (struct sockaddr *)&daemon_addr, + sizeof (daemon_addr)) != sizeof(msg)) + warn("send_delete (remote)"); + msg.id_num = htonl(local_id); + daemon_addr.sin_addr = my_machine_addr; + if (sendto(ctl_sockt, &msg, sizeof (msg), 0, + (struct sockaddr *)&daemon_addr, + sizeof (daemon_addr)) != sizeof (msg)) + warn("send_delete (local)"); +} diff --git a/usr.bin/talk/io.c b/usr.bin/talk/io.c new file mode 100644 index 0000000..f789f57 --- /dev/null +++ b/usr.bin/talk/io.c @@ -0,0 +1,139 @@ +/* $NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)io.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: io.c,v 1.14 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +/* + * This file contains the I/O handling and the exchange of + * edit characters. This connection itself is established in + * ctl.c + */ + +#include "talk.h" +#include +#include +#include +#include +#include +#include +#include + +#define A_LONG_TIME 1000000 + +/* + * The routine to do the actual talking + */ +void +talk(void) +{ + struct pollfd set[2]; + int nb; + char buf[BUFSIZ]; + + message("Connection established\007\007\007"); + current_line = 0; + + /* + * Wait on both the other process (sockt_mask) and + * standard input ( STDIN_MASK ) + */ + set[0].fd = sockt; + set[0].events = POLLIN; + set[1].fd = fileno(stdin); + set[1].events = POLLIN; + for (;;) { + nb = poll(set, 2, A_LONG_TIME * 1000); + if (nb <= 0) { + if (errno == EINTR) + continue; + /* panic, we don't know what happened */ + p_error("Unexpected error from poll"); + quit(); + } + if (set[0].revents & POLLIN) { + /* There is data on sockt */ + nb = read(sockt, buf, sizeof buf); + if (nb <= 0) { + message("Connection closed. Exiting"); + quit(); + } + display(&his_win, buf, nb); + } + if (set[1].revents & POLLIN) { + /* + * We can't make the tty non_blocking, because + * curses's output routines would screw up + */ + ioctl(0, FIONREAD, (void *) &nb); + nb = read(0, buf, nb); + display(&my_win, buf, nb); + /* might lose data here because sockt is non-blocking */ + if (nb > 0) + write(sockt, buf, nb); + } + } +} + +/* + * p_error prints the system error message on the standard location + * on the screen and then exits. (i.e. a curses version of perror) + */ +void +p_error(const char *string) +{ + wmove(my_win.x_win, current_line%my_win.x_nlines, 0); + wprintw(my_win.x_win, "[%s : %s (%d)]\n", + string, strerror(errno), errno); + wrefresh(my_win.x_win); + move(LINES-1, 0); + refresh(); + quit(); +} + +/* + * Display string in the standard location + */ +void +message(const char *string) +{ + wmove(my_win.x_win, current_line % my_win.x_nlines, 0); + wprintw(my_win.x_win, "[%s]", string); + wclrtoeol(my_win.x_win); + current_line++; + wmove(my_win.x_win, current_line % my_win.x_nlines, 0); + wrefresh(my_win.x_win); +} diff --git a/usr.bin/talk/look_up.c b/usr.bin/talk/look_up.c new file mode 100644 index 0000000..1ff3d79 --- /dev/null +++ b/usr.bin/talk/look_up.c @@ -0,0 +1,119 @@ +/* $NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)look_up.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: look_up.c,v 1.8 2012/03/20 20:34:59 matt Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include +#include +#include "talk_ctl.h" + +/* + * See if the local daemon has an invitation for us. + */ +int +check_local(void) +{ + CTL_RESPONSE response; + CTL_RESPONSE *rp = &response; + struct sockaddr addr; + + /* the rest of msg was set up in get_names */ +#ifdef MSG_EOR + /* copy new style sockaddr to old, swap family (short in old) */ + msg.ctl_addr = *(struct talkd_sockaddr *)(void *)&ctl_addr; + msg.ctl_addr.sa_family = htons(ctl_addr.sin_family); +#else + msg.ctl_addr = *(struct sockaddr *)&ctl_addr; +#endif + /* must be initiating a talk */ + if (!look_for_invite(rp)) + return (0); + /* + * There was an invitation waiting for us, + * so connect with the other (hopefully waiting) party + */ + current_state = "Waiting to connect with caller"; + do { + if (rp->addr.sa_family != AF_INET) + p_error("Response uses invalid network address"); + + (void)memcpy(&addr, &rp->addr.sa_family, sizeof(addr)); + addr.sa_family = rp->addr.sa_family; + addr.sa_len = sizeof(addr); + + errno = 0; + if (connect(sockt, &addr, sizeof(addr)) != -1) + return (1); + } while (errno == EINTR); + if (errno == ECONNREFUSED) { + /* + * The caller gave up, but his invitation somehow + * was not cleared. Clear it and initiate an + * invitation. (We know there are no newer invitations, + * the talkd works LIFO.) + */ + ctl_transact(his_machine_addr, msg, DELETE, rp); + close(sockt); + open_sockt(); + return (0); + } + p_error("Unable to connect with initiator"); + /*NOTREACHED*/ + return (0); +} + +/* + * Look for an invitation on 'machine' + */ +int +look_for_invite(CTL_RESPONSE *rp) +{ + current_state = "Checking for invitation on caller's machine"; + ctl_transact(his_machine_addr, msg, LOOK_UP, rp); + /* the switch is for later options, such as multiple invitations */ + switch (rp->answer) { + + case SUCCESS: + msg.id_num = htonl(rp->id_num); + return (1); + + default: + /* there wasn't an invitation waiting for us */ + return (0); + } +} diff --git a/usr.bin/talk/msgs.c b/usr.bin/talk/msgs.c new file mode 100644 index 0000000..bb84843 --- /dev/null +++ b/usr.bin/talk/msgs.c @@ -0,0 +1,82 @@ +/* $NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)msgs.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: msgs.c,v 1.7 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +/* + * A package to display what is happening every MSG_INTERVAL seconds + * if we are slow connecting. + */ + +#include +#include +#include +#include "talk.h" + +#define MSG_INTERVAL 4 + +const char *current_state; +int current_line = 0; + +void +disp_msg(int dummy) +{ + message(current_state); +} + +void +start_msgs(void) +{ + struct itimerval itimer; + + message(current_state); + signal(SIGALRM, disp_msg); + itimer.it_value.tv_sec = itimer.it_interval.tv_sec = MSG_INTERVAL; + itimer.it_value.tv_usec = itimer.it_interval.tv_usec = 0; + setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0); +} + +void +end_msgs(void) +{ + struct itimerval itimer; + + timerclear(&itimer.it_value); + timerclear(&itimer.it_interval); + setitimer(ITIMER_REAL, &itimer, (struct itimerval *)0); + signal(SIGALRM, SIG_DFL); +} diff --git a/usr.bin/talk/talk.1 b/usr.bin/talk/talk.1 new file mode 100644 index 0000000..20d3ee2 --- /dev/null +++ b/usr.bin/talk/talk.1 @@ -0,0 +1,143 @@ +.\" $NetBSD: talk.1,v 1.9 2007/01/08 17:10:59 christos Exp $ +.\" +.\" Copyright (c) 1983, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)talk.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd January 7, 2007 +.Dt TALK 1 +.Os +.Sh NAME +.Nm talk +.Nd talk to another user +.Sh SYNOPSIS +.Nm +.Ar person +.Op Ar ttyname +.Sh DESCRIPTION +.Nm +is a visual communication program which copies lines from your +terminal to that of another user. +.Pp +Options available: +.Bl -tag -width ttyname +.It Ar person +If you wish to talk to someone on your own machine, then +.Ar person +is just the person's login name. +If you wish to talk to a user on another host, then +.Ar person +is of the form +.Ql user@host . +.It Ar ttyname +If you wish to talk to a user who is logged in more than once, the +.Ar ttyname +argument may be used to indicate the appropriate terminal +name, where +.Ar ttyname +is of the form +.Ql ttyXX . +.El +.Pp +When first called, +.Nm +sends the message +.Bd -literal -offset indent -compact +Message from TalkDaemon@his_machine... +talk: connection requested by your_name@your_machine. +talk: respond with: talk your_name@your_machine +.Ed +.Pp +to the user you wish to talk to. +At this point, the recipient +of the message should reply by typing +.Pp +.Dl talk \ your_name@your_machine +.Pp +It doesn't matter from which machine the recipient replies, as +long as his login-name is the same. +Once communication is established, +the two parties may type simultaneously, with their output appearing +in separate windows. +Typing control-L +.Ql ^L +will cause the screen to +be reprinted, while your erase, kill, and word kill characters will +behave normally. +To exit, just type your interrupt character; +.Nm +then moves the cursor to the bottom of the screen and restores the +terminal to its previous state. +.Pp +Permission to talk may be denied or granted by use of the +.Xr mesg 1 +command. +At the outset talking is allowed. +Certain commands, in +particular +.Xr nroff 1 +and +.Xr pr 1 , +disallow messages in order to +prevent messy output. +.Sh ENVIRONMENT +If the +.Ev TALKHOST +environment variable is set, its value is used as the +.Ar hostname +the +.Nm +packets appear to be originating from. +This is useful if you wish to talk to someone on another machine and +your internal hostname does not resolve to the address of your +external interface as seen from the other machine. +.Sh FILES +.Bl -tag -width /var/run/utmp -compact +.It Pa /etc/hosts +to find the recipient's machine +.It Pa /var/run/utmp +to find the recipient's tty +.El +.Sh SEE ALSO +.Xr mail 1 , +.Xr mesg 1 , +.Xr who 1 , +.Xr write 1 +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.2 . +.Sh BUGS +The version of +.Nm +released with +.Bx 4.3 +uses a protocol that +is incompatible with the protocol used in the version released with +.Bx 4.2 . diff --git a/usr.bin/talk/talk.c b/usr.bin/talk/talk.c new file mode 100644 index 0000000..2d71253 --- /dev/null +++ b/usr.bin/talk/talk.c @@ -0,0 +1,79 @@ +/* $NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)talk.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: talk.c,v 1.8 2011/09/06 18:32:03 joerg Exp $"); +#endif /* not lint */ + +#include "talk.h" +#include + +/* + * talk: A visual form of write. Using sockets, a two way + * connection is set up between the two people talking. + * With the aid of curses, the screen is split into two + * windows, and each users text is added to the window, + * one character at a time... + * + * Written by Kipp Hickman + * + * Modified to run under 4.1a by Clem Cole and Peter Moore + * Modified to run between hosts by Peter Moore, 8/19/82 + * Modified to run under 4.1c by Peter Moore 3/17/83 + */ + +int +main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + + get_names(argc, argv); + init_display(); + open_ctl(); + open_sockt(); + start_msgs(); + if (!check_local()) + invite_remote(); + end_msgs(); + set_edit_chars(); + talk(); + /* NOTREACHED */ + return (0); +} diff --git a/usr.bin/talk/talk.h b/usr.bin/talk/talk.h new file mode 100644 index 0000000..77cfac7 --- /dev/null +++ b/usr.bin/talk/talk.h @@ -0,0 +1,89 @@ +/* $NetBSD: talk.h,v 1.9 2011/09/06 18:32:03 joerg Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)talk.h 8.1 (Berkeley) 6/6/93 + */ + +#include +#include +#include +#include +#include +#include +#include + +extern int sockt; +extern int curses_initialized; +extern int invitation_waiting; + +extern const char *current_state; +extern int current_line; + +typedef struct xwin { + WINDOW *x_win; + int x_nlines; + int x_ncols; + int x_line; + int x_col; + char kill; + char cerase; + char werase; +} xwin_t; + +extern xwin_t my_win; +extern xwin_t his_win; +extern WINDOW *line_win; + +void announce_invite(void); +int check_local(void); +void ctl_transact(struct in_addr, CTL_MSG, int, CTL_RESPONSE *); +void display(xwin_t *, char *, int); +void disp_msg(int); +void end_msgs(void); +void get_addrs(const char *, const char *); +void get_names(int, char **); +void init_display(void); +void invite_remote(void); +int look_for_invite(CTL_RESPONSE *); +int max(int, int); +void message(const char *); +void open_ctl(void); +void open_sockt(void); +void print_addr(struct sockaddr_in); +void p_error(const char *) __dead; +void quit(void) __dead; +int readwin(WINDOW *, int, int); +void re_invite(int) __dead; +void send_delete(void); +void set_edit_chars(void); +void sig_sent(int) __dead; +void start_msgs(void); +void talk(void) __dead; +void xscroll(xwin_t *, int); diff --git a/usr.bin/talk/talk_ctl.h b/usr.bin/talk/talk_ctl.h new file mode 100644 index 0000000..e4bab52 --- /dev/null +++ b/usr.bin/talk/talk_ctl.h @@ -0,0 +1,41 @@ +/* $NetBSD: talk_ctl.h,v 1.4 2003/08/07 11:16:05 agc Exp $ */ + +/* + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)talk_ctl.h 8.1 (Berkeley) 6/6/93 + */ + +extern struct sockaddr_in daemon_addr; +extern struct sockaddr_in ctl_addr; +extern struct sockaddr_in my_addr; +extern struct in_addr my_machine_addr; +extern struct in_addr his_machine_addr; +extern u_short daemon_port; +extern int ctl_sockt; +extern CTL_MSG msg; diff --git a/usr.bin/tee/tee.1 b/usr.bin/tee/tee.1 new file mode 100644 index 0000000..5ba9565 --- /dev/null +++ b/usr.bin/tee/tee.1 @@ -0,0 +1,84 @@ +.\" $NetBSD: tee.1,v 1.9 2017/07/04 07:05:16 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tee.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt TEE 1 +.Os +.Sh NAME +.Nm tee +.Nd pipe fitting +.Sh SYNOPSIS +.Nm +.Op Fl ai +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility copies standard input to standard output, +making a copy in zero or more files. +The output is unbuffered. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl a +Append the output to the files rather than +overwriting them. +.It Fl i +Ignore the +.Dv SIGINT +signal. +.El +.Pp +The following operands are available: +.Bl -tag -width file +.It file +A pathname of an output +.Ar file . +.El +.Pp +The +.Nm +utility takes the default action for all signals, +except in the event of the +.Fl i +option. +.Sh EXIT STATUS +.Ex -std tee +.Sh STANDARDS +The +.Nm +function is expected to be +.Tn POSIX +.St -p1003.2 +compatible. diff --git a/usr.bin/tee/tee.c b/usr.bin/tee/tee.c new file mode 100644 index 0000000..f534e7e --- /dev/null +++ b/usr.bin/tee/tee.c @@ -0,0 +1,149 @@ +/* $NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $ */ + +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tee.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: tee.c,v 1.12 2016/09/05 00:40:30 sevan Exp $"); +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct _list { + struct _list *next; + int fd; + const char *name; +} LIST; +LIST *head; + +void add(int, const char *); + +int +main(int argc, char *argv[]) +{ + LIST *p; + ssize_t rval; + int fd; + int append, ch, exitval; + char *buf; +#define BSIZE (8 * 1024) + + setlocale(LC_ALL, ""); + + append = 0; + while ((ch = getopt(argc, argv, "ai")) != -1) + switch((char)ch) { + case 'a': + append = 1; + break; + case 'i': + (void)signal(SIGINT, SIG_IGN); + break; + case '?': + default: + (void)fprintf(stderr, "usage: tee [-ai] [file ...]\n"); + exit(1); + } + argv += optind; + argc -= optind; + + if ((buf = malloc(BSIZE)) == NULL) + err(1, "malloc"); + + add(STDOUT_FILENO, "stdout"); + + for (exitval = 0; *argv; ++argv) + if ((fd = open(*argv, append ? O_WRONLY|O_CREAT|O_APPEND : + O_WRONLY|O_CREAT|O_TRUNC, DEFFILEMODE)) < 0) { + warn("%s", *argv); + exitval = 1; + } else + add(fd, *argv); + + while ((rval = read(STDIN_FILENO, buf, BSIZE)) > 0) + for (p = head; p; p = p->next) { + const char *bp = buf; + size_t n = rval; + ssize_t wval; + + do { + if ((wval = write(p->fd, bp, n)) == -1) { + warn("%s", p->name); + exitval = 1; + break; + } + bp += wval; + } while (n -= wval); + } + if (rval < 0) { + warn("read"); + exitval = 1; + } + + for (p = head; p; p = p->next) { + if (close(p->fd) == -1) { + warn("%s", p->name); + exitval = 1; + } + } + + exit(exitval); +} + +void +add(int fd, const char *name) +{ + LIST *p; + + if ((p = malloc(sizeof(LIST))) == NULL) + err(1, "malloc"); + p->fd = fd; + p->name = name; + p->next = head; + head = p; +} diff --git a/usr.bin/time/ext.h b/usr.bin/time/ext.h new file mode 100644 index 0000000..6626a29 --- /dev/null +++ b/usr.bin/time/ext.h @@ -0,0 +1,5 @@ +/* $NetBSD: ext.h,v 1.3 2017/07/15 14:34:08 christos Exp $ */ + +/* borrowed from ../../bin/csh/extern.h */ +void prusage1(FILE *, const char *fmt, struct rusage *, struct rusage *, + struct timespec *, struct timespec *); diff --git a/usr.bin/time/time.1 b/usr.bin/time/time.1 new file mode 100644 index 0000000..3f42310 --- /dev/null +++ b/usr.bin/time/time.1 @@ -0,0 +1,220 @@ +.\" $NetBSD: time.1,v 1.28 2017/07/15 14:40:36 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)time.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd July 15, 2017 +.Dt TIME 1 +.Os +.Sh NAME +.Nm time +.Nd time command execution +.Sh SYNOPSIS +.Nm +.Op Fl clp +.Op Fl f Ar fmt +.Ar command +.Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +utility +executes and +times +.Ar command . +After the command finishes, +.Nm +writes the total elapsed time (wall clock time), +.Pq Dq real , +the CPU time spent executing +.Ar command +at user level +.Pq Dq user , +and the CPU time spent executing in the operating system kernel +.Pq Dq sys , +to the standard error stream. +Times are reported in seconds. +.Pp +Available options: +.Bl -tag -width Ds +.It Fl c +Displays information in the format used by default the +.Nm +builtin of +.Xr csh 1 +uses (%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww). +.It Fl f +Specify a time format using the +.Xr csh 1 +.Nm +builtin syntax. +The following sequences may be used in the format string: +.Pp +.Bl -tag -width 4n -compact +.It \&%U +The time the process spent in user mode in cpu seconds. +.It \&%S +The time the process spent in kernel mode in cpu seconds. +.It \&%E +The elapsed (wall clock) time in seconds. +.It \&%P +The CPU percentage computed as (%U + %S) / %E. +.It \&%W +Number of times the process was swapped. +.It \&%X +The average amount in (shared) text space used in Kbytes. +.It \&%D +The average amount in (unshared) data/stack space used in Kbytes. +.It \&%K +The total space used (%X + %D) in Kbytes. +.It \&%M +The maximum memory the process had in use at any time in Kbytes. +.It \&%F +The number of major page faults (page needed to be brought from disk). +.It \&%R +The number of minor page faults. +.It \&%I +The number of input operations. +.It \&%O +The number of output operations. +.It \&%r +The number of socket messages received. +.It \&%s +The number of socket messages sent. +.It \&%k +The number of signals received. +.It \&%w +The number of voluntary context switches (waits). +.It \&%c +The number of involuntary context switches. +.El +.It Fl l +Lists resource utilization information. +The contents of the +.Ar command +process's +.Em rusage +structure are printed; see below. +.It Fl p +The output is formatted as specified by +.St -p1003.2-92 . +.El +.Pp +Some shells, such as +.Xr csh 1 +and +.Xr ksh 1 , +have their own and syntactically different built-in version of +.Nm . +The utility described here +is available as +.Pa /usr/bin/time +to users of these shells. +.Ss Resource Utilization +If the +.Fl l +option is given, the following resource usage +information is displayed +in addition to the timing information: +.Bl -item -offset indent -compact +.It +maximum resident set size +.It +average shared memory size +.It +average unshared data size +.It +average unshared stack size +.It +page reclaims +.It +page faults +.It +swaps +.It +block input operations +.It +block output operations +.It +messages sent +.It +messages received +.It +signals received +.It +voluntary context switches +.It +involuntary context switches +.El +Resource usage is the total for the execution of +.Ar command +and any child processes it spawns, as per +.Xr wait4 2 . +.Sh FILES +.Bl -tag -width Xsys/resource.hX -compact +.It Aq sys/resource.h +.El +.Sh EXIT STATUS +The +.Nm +utility exits with one of the following values: +.Bl -tag -width indent +.It 1-125 +An error occurred in the +.Nm +utility. +.It 126 +The +.Ar command +was found but could not be invoked. +.It 127 +The +.Ar command +could not be found. +.El +.Pp +Otherwise, the exit status of +.Nm +will be that of +.Ar command . +.Sh SEE ALSO +.Xr csh 1 , +.Xr ksh 1 , +.Xr clock_gettime 2 , +.Xr getrusage 2 , +.Xr wait4 2 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +.Sh BUGS +The granularity of seconds on microprocessors is crude and +can result in times being reported for CPU usage which are too large by +a second. diff --git a/usr.bin/time/time.c b/usr.bin/time/time.c new file mode 100644 index 0000000..263db9a --- /dev/null +++ b/usr.bin/time/time.c @@ -0,0 +1,215 @@ +/* $NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $ */ + +/* + * Copyright (c) 1987, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1987, 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)time.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: time.c,v 1.23 2017/07/15 14:34:08 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext.h" + +__dead static void usage(void); +static void prl(long, const char *); +static void prts(const char *, const char *, const struct timespec *, + const char *); +static void prtv(const char *, const char *, const struct timeval *, + const char *); + +int +main(int argc, char ** volatile argv) +{ + int pid; + int ch, status; + int volatile portableflag; + int volatile lflag; + const char *decpt; + const char *fmt; + const struct lconv *lconv; + struct timespec before, after; + struct rusage ru; + + (void)setlocale(LC_ALL, ""); + + lflag = portableflag = 0; + fmt = NULL; + while ((ch = getopt(argc, argv, "cf:lp")) != -1) { + switch (ch) { + case 'f': + fmt = optarg; + portableflag = 0; + lflag = 0; + break; + case 'c': + fmt = "%Uu %Ss %E %P %X+%Dk %I+%Oio %Fpf+%Ww"; + portableflag = 0; + lflag = 0; + break; + case 'p': + portableflag = 1; + fmt = NULL; + lflag = 0; + break; + case 'l': + lflag = 1; + portableflag = 0; + fmt = NULL; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) + usage(); + + (void)clock_gettime(CLOCK_MONOTONIC, &before); + switch(pid = vfork()) { + case -1: /* error */ + err(EXIT_FAILURE, "Vfork failed"); + /* NOTREACHED */ + case 0: /* child */ + /* LINTED will return only on failure */ + execvp(*argv, argv); + err((errno == ENOENT) ? 127 : 126, "Can't exec `%s'", *argv); + /* NOTREACHED */ + } + + /* parent */ + (void)signal(SIGINT, SIG_IGN); + (void)signal(SIGQUIT, SIG_IGN); + if ((pid = wait4(pid, &status, 0, &ru)) == -1) + err(EXIT_FAILURE, "wait4 %d failed", pid); + (void)clock_gettime(CLOCK_MONOTONIC, &after); + if (!WIFEXITED(status)) + warnx("Command terminated abnormally."); + timespecsub(&after, &before, &after); + + if ((lconv = localeconv()) == NULL || + (decpt = lconv->decimal_point) == NULL) + decpt = "."; + + if (fmt) { + static struct rusage null_ru; + before.tv_sec = 0; + before.tv_nsec = 0; + prusage1(stderr, fmt, &null_ru, &ru, &after, &before); + } else if (portableflag) { + prts("real ", decpt, &after, "\n"); + prtv("user ", decpt, &ru.ru_utime, "\n"); + prtv("sys ", decpt, &ru.ru_stime, "\n"); + } else { + prts("", decpt, &after, " real "); + prtv("", decpt, &ru.ru_utime, " user "); + prtv("", decpt, &ru.ru_stime, " sys\n"); + } + + if (lflag) { + int hz = (int)sysconf(_SC_CLK_TCK); + unsigned long long ticks; +#define SCALE(x) (long)(ticks ? x / ticks : 0) + + ticks = hz * (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) + + hz * (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec) / 1000000; + prl(ru.ru_maxrss, "maximum resident set size"); + prl(SCALE(ru.ru_ixrss), "average shared memory size"); + prl(SCALE(ru.ru_idrss), "average unshared data size"); + prl(SCALE(ru.ru_isrss), "average unshared stack size"); + prl(ru.ru_minflt, "page reclaims"); + prl(ru.ru_majflt, "page faults"); + prl(ru.ru_nswap, "swaps"); + prl(ru.ru_inblock, "block input operations"); + prl(ru.ru_oublock, "block output operations"); + prl(ru.ru_msgsnd, "messages sent"); + prl(ru.ru_msgrcv, "messages received"); + prl(ru.ru_nsignals, "signals received"); + prl(ru.ru_nvcsw, "voluntary context switches"); + prl(ru.ru_nivcsw, "involuntary context switches"); + } + + return (WIFEXITED(status) ? WEXITSTATUS(status) : EXIT_FAILURE); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "Usage: %s [-clp] [-f ] utility [argument ...]\n", + getprogname()); + exit(EXIT_FAILURE); +} + +static void +prl(long val, const char *expn) +{ + + (void)fprintf(stderr, "%10ld %s\n", val, expn); +} + +static void +prts(const char *pre, const char *decpt, const struct timespec *ts, + const char *post) +{ + + (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)ts->tv_sec, + decpt, (long)ts->tv_nsec / 10000000, post); +} + +static void +prtv(const char *pre, const char *decpt, const struct timeval *tv, + const char *post) +{ + + (void)fprintf(stderr, "%s%9lld%s%02ld%s", pre, (long long)tv->tv_sec, + decpt, (long)tv->tv_usec / 10000, post); +} diff --git a/usr.bin/time/xtime.c b/usr.bin/time/xtime.c new file mode 100644 index 0000000..b685d2d --- /dev/null +++ b/usr.bin/time/xtime.c @@ -0,0 +1,9 @@ +/* $NetBSD: xtime.c,v 1.1 2007/02/24 21:30:27 matt Exp $ */ + + + +#include +#include +#include +#include "ext.h" +#include "csh/time.c" diff --git a/usr.bin/touch/touch.1 b/usr.bin/touch/touch.1 new file mode 100644 index 0000000..d1d1fb4 --- /dev/null +++ b/usr.bin/touch/touch.1 @@ -0,0 +1,215 @@ +.\" $NetBSD: touch.1,v 1.26 2016/12/24 15:49:18 abhinav Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)touch.1 8.3 (Berkeley) 4/28/95 +.\" +.Dd December 24, 2016 +.Dt TOUCH 1 +.Os +.Sh NAME +.Nm touch +.Nd change file access and modification times +.Sh SYNOPSIS +.Nm +.Op Fl acfhm +.Op Fl d Ar human-datetime +.Op Fl Fl date Ar human-datetime +.Op Fl r Ar file +.Op Fl Fl reference Ar file +.Op Fl t Ar datetime +.Ar file ... +.Sh DESCRIPTION +The +.Nm +utility changes the access and modification times of files to the +current time of day. +If the file doesn't exist, it is created with default permissions. +.Pp +The following options are available: +.Bl -tag -width "-d human-datetime" +.It Fl a +Change the access time of the file. +The modification time of the file is not changed unless the +.Fl m +flag is also specified. +.It Fl c +Do not create the file if it does not exist. +The +.Nm +utility does not treat this as an error. +No error messages are displayed and the exit value is not affected. +.It Fl d Ar human-datetime +.It Fl Fl date Ar human-datetime +Parse +.Ar human-datetime +using the human datetime parser +.Xr parsedate 3 . +.It Fl f +This flag has no effect; it is accepted for compatibility reasons. +.It Fl h +If +.Ar file +is a symbolic link, access and/or modification time of the link is changed. +This option implies +.Fl c . +.It Fl m +Change the modification time of the file. +The access time of the file is not changed unless the +.Fl a +flag is also specified. +.It Fl r Ar file +.It Fl Fl reference Ar file +Use the access and modifications times from +.Ar file +instead of the current time of day. +.It Fl t Ar datetime +Change the access and modification times to the specified time. +The argument +.Ar datetime +should be in the form +.Dq [[CC]YY]MMDDhhmm[.SS] +where each pair of letters represents the following: +.Pp +.Bl -tag -width Ds -compact -offset indent +.It Ar CC +The first two digits of the year (the century). +.It Ar YY +The second two digits of the year. +If +.Dq YY +is specified, but +.Dq CC +is not, a value for +.Dq YY +between 69 and 99 results in a +.Dq CC +value of 19. +Otherwise, a +.Dq CC +value of 20 is used. +.It Ar MM +The month of the year, from 1 to 12. +.It Ar DD +The day of the month, from 1 to 31. +.It Ar hh +The hour of the day, from 0 to 23. +.It Ar mm +The minute of the hour, from 0 to 59. +.It Ar SS +The second of the minute, from 0 to 60 (permitting leap seconds). +If +.Ar SS +is 60 and the resulting time, +as affected by the +.Ev TZ +environment variable, +does not refer to a leap second, +the resulting time is one second after a time where +.Ar SS +is 59. +If +.Ar SS +is not given a value, it is assumed to be zero. +.El +.Pp +If the +.Dq CC +and +.Dq YY +letter pairs are not specified, the values default to the current +year. +If the +.Dq SS +letter pair is not specified, the value defaults to 0. +.El +.Pp +The +.Fl d , +.Fl r , +and +.Fl t +options are mutually exclusive. +If more than one of these options is present, the last one is used. +.Sh ENVIRONMENT +.Bl -tag -width -iTZ +.It Ev TZ +The timezone to be used for interpreting the +.Ar datetime +argument of the +.Fl t +option. +.El +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The obsolescent form of +.Nm , +where a time format is specified as the first argument, is supported. +When no +.Fl d , +.Fl r , +or +.Fl t +option is specified, there are at least two arguments, and the first +argument is a string of digits either eight or ten characters in length, +the first argument is interpreted as a time specification of the form +.Dq MMDDhhmm[YY] . +.Pp +The +.Dq MM , +.Dq DD , +.Dq hh +and +.Dq mm +letter pairs are treated as their counterparts specified to the +.Fl t +option. +If the +.Dq YY +letter pair is in the range 69 to 99, the year is set to 1969 to 1999, +otherwise, the year is set in the 21st century. +.Sh SEE ALSO +.Xr utimes 2 , +.Xr parsedate 3 +.Sh STANDARDS +The +.Nm +utility is expected to be a superset of the +.St -p1003.2 +specification. +.Sh HISTORY +A +.Nm +utility appeared in +.At v7 . +.Sh BUGS +A symbolic link can't be a reference file of access and/or modification time. diff --git a/usr.bin/touch/touch.c b/usr.bin/touch/touch.c new file mode 100644 index 0000000..1963815 --- /dev/null +++ b/usr.bin/touch/touch.c @@ -0,0 +1,337 @@ +/* $NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $ */ + +/* + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)touch.c 8.2 (Berkeley) 4/28/95"; +#endif +__RCSID("$NetBSD: touch.c,v 1.33 2015/03/02 03:17:24 enami Exp $"); +#endif /* not lint */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void stime_arg0(char *, struct timespec *); +static void stime_arg1(char *, struct timespec *); +static void stime_arg2(char *, int, struct timespec *); +static void stime_file(char *, struct timespec *); +__dead static void usage(void); + +struct option touch_longopts[] = { + { "date", required_argument, 0, + 'd' }, + { "reference", required_argument, 0, + 'r' }, + { NULL, 0, 0, + 0 }, +}; + +int +main(int argc, char *argv[]) +{ + struct stat sb; + struct timespec ts[2]; + int aflag, cflag, hflag, mflag, ch, fd, len, rval, timeset; + char *p; + int (*change_file_times)(const char *, const struct timespec *); + int (*get_file_status)(const char *, struct stat *); + + setlocale(LC_ALL, ""); + + aflag = cflag = hflag = mflag = timeset = 0; + if (clock_gettime(CLOCK_REALTIME, &ts[0])) + err(1, "clock_gettime"); + + while ((ch = getopt_long(argc, argv, "acd:fhmr:t:", touch_longopts, + NULL)) != -1) + switch(ch) { + case 'a': + aflag = 1; + break; + case 'c': + cflag = 1; + break; + case 'd': + timeset = 1; + stime_arg0(optarg, ts); + break; + case 'f': + break; + case 'h': + hflag = 1; + break; + case 'm': + mflag = 1; + break; + case 'r': + timeset = 1; + stime_file(optarg, ts); + break; + case 't': + timeset = 1; + stime_arg1(optarg, ts); + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + /* Default is both -a and -m. */ + if (aflag == 0 && mflag == 0) + aflag = mflag = 1; + + if (hflag) { + cflag = 1; /* Don't create new file */ + change_file_times = lutimens; + get_file_status = lstat; + } else { + change_file_times = utimens; + get_file_status = stat; + } + + /* + * If no -r or -t flag, at least two operands, the first of which + * is an 8 or 10 digit number, use the obsolete time specification. + */ + if (!timeset && argc > 1) { + (void)strtol(argv[0], &p, 10); + len = p - argv[0]; + if (*p == '\0' && (len == 8 || len == 10)) { + timeset = 1; + stime_arg2(*argv++, len == 10, ts); + } + } + + /* Otherwise use the current time of day. */ + if (!timeset) + ts[1] = ts[0]; + + if (*argv == NULL) + usage(); + + for (rval = EXIT_SUCCESS; *argv; ++argv) { + /* See if the file exists. */ + if ((*get_file_status)(*argv, &sb)) { + if (!cflag) { + /* Create the file. */ + fd = open(*argv, + O_WRONLY | O_CREAT, DEFFILEMODE); + if (fd == -1 || fstat(fd, &sb) || close(fd)) { + rval = EXIT_FAILURE; + warn("%s", *argv); + continue; + } + + /* If using the current time, we're done. */ + if (!timeset) + continue; + } else + continue; + } + if (!aflag) + ts[0] = sb.st_atimespec; + if (!mflag) + ts[1] = sb.st_mtimespec; + + /* Try utimes(2). */ + if (!(*change_file_times)(*argv, ts)) + continue; + + /* If the user specified a time, nothing else we can do. */ + if (timeset) { + rval = EXIT_FAILURE; + warn("%s", *argv); + } + + /* + * System V and POSIX 1003.1 require that a NULL argument + * set the access/modification times to the current time. + * The permission checks are different, too, in that the + * ability to write the file is sufficient. Take a shot. + */ + if (!(*change_file_times)(*argv, NULL)) + continue; + + rval = EXIT_FAILURE; + warn("%s", *argv); + } + exit(rval); +} + +#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) + +static void +stime_arg0(char *arg, struct timespec *tsp) +{ + tsp[1].tv_sec = tsp[0].tv_sec = parsedate(arg, NULL, NULL); + if (tsp[0].tv_sec == -1) + errx(EXIT_FAILURE, "Could not parse `%s'", arg); + tsp[0].tv_nsec = tsp[1].tv_nsec = 0; +} + +static void +stime_arg1(char *arg, struct timespec *tsp) +{ + struct tm *t; + time_t tmptime; + int yearset; + char *p; + /* Start with the current time. */ + tmptime = tsp[0].tv_sec; + if ((t = localtime(&tmptime)) == NULL) + err(EXIT_FAILURE, "localtime"); + /* [[CC]YY]MMDDhhmm[.SS] */ + if ((p = strchr(arg, '.')) == NULL) + t->tm_sec = 0; /* Seconds defaults to 0. */ + else { + if (strlen(p + 1) != 2) + goto terr; + *p++ = '\0'; + t->tm_sec = ATOI2(p); + } + + yearset = 0; + switch (strlen(arg)) { + case 12: /* CCYYMMDDhhmm */ + t->tm_year = ATOI2(arg) * 100 - TM_YEAR_BASE; + yearset = 1; + /* FALLTHROUGH */ + case 10: /* YYMMDDhhmm */ + if (yearset) { + t->tm_year += ATOI2(arg); + } else { + yearset = ATOI2(arg); + if (yearset < 69) + t->tm_year = yearset + 2000 - TM_YEAR_BASE; + else + t->tm_year = yearset + 1900 - TM_YEAR_BASE; + } + /* FALLTHROUGH */ + case 8: /* MMDDhhmm */ + t->tm_mon = ATOI2(arg); + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + /* FALLTHROUGH */ + case 6: + t->tm_mday = ATOI2(arg); + /* FALLTHROUGH */ + case 4: + t->tm_hour = ATOI2(arg); + /* FALLTHROUGH */ + case 2: + t->tm_min = ATOI2(arg); + break; + default: + goto terr; + } + + t->tm_isdst = -1; /* Figure out DST. */ + tsp[0].tv_sec = tsp[1].tv_sec = mktime(t); + if (tsp[0].tv_sec == -1) +terr: errx(EXIT_FAILURE, + "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); + + tsp[0].tv_nsec = tsp[1].tv_nsec = 0; +} + +static void +stime_arg2(char *arg, int year, struct timespec *tsp) +{ + struct tm *t; + time_t tmptime; + /* Start with the current time. */ + tmptime = tsp[0].tv_sec; + if ((t = localtime(&tmptime)) == NULL) + err(EXIT_FAILURE, "localtime"); + + t->tm_mon = ATOI2(arg); /* MMDDhhmm[yy] */ + --t->tm_mon; /* Convert from 01-12 to 00-11 */ + t->tm_mday = ATOI2(arg); + t->tm_hour = ATOI2(arg); + t->tm_min = ATOI2(arg); + if (year) { + year = ATOI2(arg); + if (year < 69) + t->tm_year = year + 2000 - TM_YEAR_BASE; + else + t->tm_year = year + 1900 - TM_YEAR_BASE; + } + t->tm_sec = 0; + + t->tm_isdst = -1; /* Figure out DST. */ + tsp[0].tv_sec = tsp[1].tv_sec = mktime(t); + if (tsp[0].tv_sec == -1) + errx(EXIT_FAILURE, + "out of range or illegal time specification: MMDDhhmm[yy]"); + + tsp[0].tv_nsec = tsp[1].tv_nsec = 0; +} + +static void +stime_file(char *fname, struct timespec *tsp) +{ + struct stat sb; + + if (stat(fname, &sb)) + err(1, "%s", fname); + tsp[0] = sb.st_atimespec; + tsp[1] = sb.st_mtimespec; +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-acfhm] [-d|--date datetime] [-r|--reference file] [-t time] file ...\n", + getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/tput/clear.sh b/usr.bin/tput/clear.sh new file mode 100644 index 0000000..5fffaf0 --- /dev/null +++ b/usr.bin/tput/clear.sh @@ -0,0 +1,38 @@ +#!/bin/sh - +# $NetBSD: clear.sh,v 1.2 1994/12/07 08:49:09 jtc Exp $ +# +# Copyright (c) 1989, 1993 +# The Regents of the University of California. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# This product includes software developed by the University of +# California, Berkeley and its contributors. +# 4. Neither the name of the University nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# @(#)clear.sh 8.1 (Berkeley) 6/6/93 +# + +exec tput clear diff --git a/usr.bin/tput/tput.1 b/usr.bin/tput/tput.1 new file mode 100644 index 0000000..1ea0bb4 --- /dev/null +++ b/usr.bin/tput/tput.1 @@ -0,0 +1,139 @@ +.\" $NetBSD: tput.1,v 1.16 2012/04/21 12:27:30 roy Exp $ +.\" +.\" Copyright (c) 1989, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tput.1 8.2 (Berkeley) 3/19/94 +.\" +.Dd April 5, 2012 +.Dt TPUT 1 +.Os +.Sh NAME +.Nm tput , +.Nm clear +.Nd terminal capability interface +.Sh SYNOPSIS +.Nm +.Op Fl T Ar term +.Ar attribute +.Op Ar attribute-args +.Ar ... +.Sh DESCRIPTION +.Nm +makes terminal-dependent information available to users or shell +applications. +The options are as follows: +.Bl -tag -width Ds +.It Fl T +The terminal name as specified in the +.Xr terminfo 5 +database, for example, +.Dq vt100 +or +.Dq xterm . +If not specified, +.Nm +retrieves the +.Dq Ev TERM +variable from the environment. +.El +.Pp +.Nm +outputs a string if the +.Ar attribute +is of type string; a number if it is of type integer. +Otherwise, +.Nm +exits 0 if the terminal has the capability and 1 if it does not, +without further action. +.Pp +If the +.Ar attribute +is of type string, and takes arguments (e.g. cursor movement, +the terminfo +.Dq cup +sequence) the arguments are taken from the command line immediately +following the attribute. +.Pp +The following special attributes are available: +.Bl -tag -width Ar +.It clear +Clear the screen (the +.Xr terminfo 5 +.Dq cl +sequence). +.It init +Initialize the terminal (the +.Xr terminfo 5 +.Dq is2 +sequence). +.It longname +Print the descriptive name of the user's terminal type. +.It reset +Reset the terminal (the +.Xr terminfo 5 +.Dq rs1 , rs2 , rs3 +and +.Dq rf +sequence). +.El +.Sh EXIT STATUS +The exit status of +.Nm +is based on the last attribute specified. +If the attribute is of type string or of type integer, +.Nm +exits 0 if the attribute is defined for this terminal type and 1 +if it is not. +If the attribute is of type boolean, +.Nm +exits 0 if the terminal has this attribute, and 1 if it does not. +.Nm +exits 2 if any error occurred. +.Sh EXAMPLES +.Bl -tag -width "tput cm 6 11 DC 6" -compact +.It Li "tput cl cm 5 10" +clear the screen and goto line 5 column 10 +.Pp +.It Li "tput cm 6 11 DC 6" +goto line 6 column 11 and delete 6 characters +.El +.Sh SEE ALSO +.Xr termcap 3 , +.Xr terminfo 3 , +.Xr terminfo 5 +.Sh HISTORY +The +.Nm +command appears in +.Bx 4.4 . +.Sh BUGS +.Nm +can't really distinguish between different types of attributes. +.Pp +Not all terminfo entries contain the reset sequence, so using the init +sequence may be more useful. diff --git a/usr.bin/tput/tput.c b/usr.bin/tput/tput.c new file mode 100644 index 0000000..0e8da2f --- /dev/null +++ b/usr.bin/tput/tput.c @@ -0,0 +1,195 @@ +/* $NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $ */ + +/*- + * Copyright (c) 1980, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tput.c 8.3 (Berkeley) 4/28/95"; +#endif +__RCSID("$NetBSD: tput.c,v 1.26 2013/02/05 11:31:56 roy Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void usage(void) __dead; +static char **process(const char *, const char *, char **); + +int +main(int argc, char **argv) +{ + int ch, exitval, n; + char *term; + const char *p, *s; + size_t pl; + + term = NULL; + while ((ch = getopt(argc, argv, "T:")) != -1) + switch(ch) { + case 'T': + term = optarg; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!term && !(term = getenv("TERM"))) + errx(2, "No terminal type specified and no TERM " + "variable set in the environment."); + setupterm(term, 0, NULL); + for (exitval = 0; (p = *argv) != NULL; ++argv) { + switch (*p) { + case 'c': + if (!strcmp(p, "clear")) + p = "clear"; + break; + case 'i': + if (!strcmp(p, "init")) { + s = tigetstr("is1"); + if (s != NULL) + putp(s); + p = "is2"; + } + break; + case 'l': + if (!strcmp(p, "longname")) { + (void)printf("%s\n", longname()); + continue; + } + break; + case 'r': + if (!strcmp(p, "reset")) { + s = tigetstr("rs1"); + if (s != NULL) + putp(s); + p = "rs2"; + } + break; + } + pl = strlen(p); + if (((s = tigetstr(p)) != NULL && s != (char *)-1) || + (pl <= 2 && (s = tgetstr(p, NULL)) != NULL)) + argv = process(p, s, argv); + else if ((((n = tigetnum(p)) != -1 && n != -2 ) || + (pl <= 2 && (n = tgetnum(p)) != -1))) + (void)printf("%d\n", n); + else { + exitval = tigetflag(p); + if (exitval == -1) { + if (pl <= 2) + exitval = !tgetflag(p); + else + exitval = 1; + } else + exitval = !exitval; + } + + if (argv == NULL) + break; + } + return argv ? exitval : 2; +} + +static char ** +process(const char *cap, const char *str, char **argv) +{ + static const char errfew[] = + "Not enough arguments (%d) for capability `%s'"; + static const char erresc[] = + "Unknown %% escape (%s) for capability `%s'"; + static const char errnum[] = + "Expected a numeric argument [%d] (%s) for capability `%s'"; + static const char errcharlong[] = + "Platform does not fit a string into a long for capability '%s'"; + int i, nparams, piss[TPARM_MAX]; + long nums[TPARM_MAX]; + char *strs[TPARM_MAX], *tmp; + + /* Count how many values we need for this capability. */ + errno = 0; + memset(&piss, 0, sizeof(piss)); + nparams = _ti_parm_analyse(str, piss, TPARM_MAX); + if (errno == EINVAL) + errx(2, erresc, str, cap); + + /* Create our arrays of integers and strings */ + for (i = 0; i < nparams; i++) { + if (*++argv == NULL || *argv[0] == '\0') + errx(2, errfew, nparams, cap); + if (piss[i]) { + if (sizeof(char *) > sizeof(long) /* CONSTCOND */) + errx(2, errcharlong, cap); + strs[i] = *argv; + } else { + errno = 0; + nums[i] = strtol(*argv, &tmp, 0); + if ((errno == ERANGE && + (nums[i] == LONG_MIN || nums[i] == LONG_MAX)) || + (errno != 0 && nums[i] == 0) || + tmp == str || + *tmp != '\0') + errx(2, errnum, i + 1, *argv, cap); + } + } + + /* And output */ +#define p(i) (i <= nparams ? \ + (piss[i - 1] ? (long)strs[i - 1] : nums[i - 1]) : 0) + putp(tparm(str, p(1), p(2), p(3), p(4), p(5), p(6), p(7), p(8), p(9))); + + return argv; +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "Usage: %s [-T term] attribute [attribute-args] ...\n", + getprogname()); + exit(2); +} diff --git a/usr.bin/tr/extern.h b/usr.bin/tr/extern.h new file mode 100644 index 0000000..8cb2de9 --- /dev/null +++ b/usr.bin/tr/extern.h @@ -0,0 +1,43 @@ +/* $NetBSD: extern.h,v 1.11 2013/08/11 00:39:22 dholland Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)extern.h 8.1 (Berkeley) 6/6/93 + */ + +struct str; +typedef struct str STR; + +#include +#define NCHARS (UCHAR_MAX + 1) /* Number of possible characters. */ +#define OOBCH (UCHAR_MAX + 1) /* Out of band character value. */ + +STR *str_create(int, const char *); +void str_destroy(STR *); +int next(STR *, int *); diff --git a/usr.bin/tr/str.c b/usr.bin/tr/str.c new file mode 100644 index 0000000..a9dccde --- /dev/null +++ b/usr.bin/tr/str.c @@ -0,0 +1,453 @@ +/* $NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $ */ + +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +#if 0 +static char sccsid[] = "@(#)str.c 8.2 (Berkeley) 4/28/95"; +#endif +__RCSID("$NetBSD: str.c,v 1.30 2018/05/26 11:20:30 leot Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "extern.h" + +struct str { + enum { STRING1, STRING2 } which; + enum { EOS, INFINITE, NORMAL, RANGE, SEQUENCE, SET } state; + int cnt; /* character count */ + int lastch; /* last character */ + int equiv[2]; /* equivalence set */ + int *set; /* set of characters */ + const char *str; /* user's string */ +}; + +static int backslash(STR *); +static int bracket(STR *); +static int c_class(const void *, const void *); +static int *genclass(const char *, size_t); +static void genequiv(STR *); +static int genrange(STR *); +static void genseq(STR *); + +STR * +str_create(int whichstring, const char *txt) +{ + STR *s; + + s = malloc(sizeof(*s)); + if (s == NULL) { + err(1, "Out of memory"); + } + + s->which = whichstring == 2 ? STRING2 : STRING1; + s->state = NORMAL; + s->cnt = 0; + s->lastch = OOBCH; + s->equiv[0] = 0; + s->equiv[1] = OOBCH; + s->set = NULL; + s->str = txt; + + return s; +} + +void +str_destroy(STR *s) +{ + if (s->set != NULL && s->set != s->equiv) { + free(s->set); + } + free(s); +} + +int +next(STR *s, int *ret) +{ + int ch; + + switch (s->state) { + case EOS: + *ret = s->lastch; + return 0; + case INFINITE: + *ret = s->lastch; + return 1; + case NORMAL: + ch = (unsigned char)s->str[0]; + switch (ch) { + case '\0': + s->state = EOS; + *ret = s->lastch; + return 0; + case '\\': + s->lastch = backslash(s); + break; + case '[': + if (bracket(s)) { + return next(s, ret); + } + /* FALLTHROUGH */ + default: + ++s->str; + s->lastch = ch; + break; + } + + /* We can start a range at any time. */ + if (s->str[0] == '-' && genrange(s)) { + return next(s, ret); + } + *ret = s->lastch; + return 1; + case RANGE: + if (s->cnt == 0) { + s->state = NORMAL; + return next(s, ret); + } + s->cnt--; + ++s->lastch; + *ret = s->lastch; + return 1; + case SEQUENCE: + if (s->cnt == 0) { + s->state = NORMAL; + return next(s, ret); + } + s->cnt--; + *ret = s->lastch; + return 1; + case SET: + s->lastch = s->set[s->cnt++]; + if (s->lastch == OOBCH) { + s->state = NORMAL; + if (s->set != s->equiv) { + free(s->set); + } + s->set = NULL; + return next(s, ret); + } + *ret = s->lastch; + return 1; + } + /* NOTREACHED */ + assert(0); + *ret = s->lastch; + return 0; +} + +static int +bracket(STR *s) +{ + const char *p; + int *q; + + switch (s->str[1]) { + case ':': /* "[:class:]" */ + if ((p = strstr(s->str + 2, ":]")) == NULL) + return 0; + s->str += 2; + q = genclass(s->str, p - s->str); + s->state = SET; + s->set = q; + s->cnt = 0; + s->str = p + 2; + return 1; + case '=': /* "[=equiv=]" */ + if ((p = strstr(s->str + 2, "=]")) == NULL) + return 0; + s->str += 2; + genequiv(s); + s->str = p + 2; + return 1; + default: /* "[\###*n]" or "[#*n]" */ + if ((p = strpbrk(s->str + 2, "*]")) == NULL) + return 0; + if (p[0] != '*' || strchr(p, ']') == NULL) + return 0; + s->str += 1; + genseq(s); + return 1; + } + /* NOTREACHED */ +} + +typedef struct { + const char *name; + int (*func)(int); +} CLASS; + +static const CLASS classes[] = { + { "alnum", isalnum }, + { "alpha", isalpha }, + { "blank", isblank }, + { "cntrl", iscntrl }, + { "digit", isdigit }, + { "graph", isgraph }, + { "lower", islower }, + { "print", isprint }, + { "punct", ispunct }, + { "space", isspace }, + { "upper", isupper }, + { "xdigit", isxdigit }, +}; + +typedef struct { + const char *name; + size_t len; +} CLASSKEY; + +static int * +genclass(const char *class, size_t len) +{ + int ch; + const CLASS *cp; + CLASSKEY key; + int *p; + unsigned pos, num; + + /* Find the class */ + key.name = class; + key.len = len; + cp = bsearch(&key, classes, __arraycount(classes), sizeof(classes[0]), + c_class); + if (cp == NULL) { + errx(1, "unknown class %.*s", (int)len, class); + } + + /* + * Figure out what characters are in the class + */ + + num = NCHARS + 1; + p = malloc(num * sizeof(*p)); + if (p == NULL) { + err(1, "malloc"); + } + + pos = 0; + for (ch = 0; ch < NCHARS; ch++) { + if (cp->func(ch)) { + p[pos++] = ch; + } + } + + p[pos++] = OOBCH; + for (; pos < num; pos++) { + p[pos] = 0; + } + + return p; +} + +static int +c_class(const void *av, const void *bv) +{ + const CLASSKEY *a = av; + const CLASS *b = bv; + size_t blen; + int r; + + blen = strlen(b->name); + r = strncmp(a->name, b->name, a->len); + if (r != 0) { + return r; + } + if (a->len < blen) { + /* someone gave us a prefix of the right name */ + return -1; + } + assert(a-> len == blen); + return 0; +} + +/* + * English doesn't have any equivalence classes, so for now + * we just syntax check and grab the character. + */ +static void +genequiv(STR *s) +{ + int ch; + + ch = (unsigned char)s->str[0]; + if (ch == '\\') { + s->equiv[0] = backslash(s); + } else { + s->equiv[0] = ch; + s->str++; + } + if (s->str[0] != '=') { + errx(1, "Misplaced equivalence equals sign"); + } + s->str++; + if (s->str[0] != ']') { + errx(1, "Misplaced equivalence right bracket"); + } + s->str++; + + s->cnt = 0; + s->state = SET; + s->set = s->equiv; +} + +static int +genrange(STR *s) +{ + int stopval; + const char *savestart; + + savestart = s->str++; + stopval = s->str[0] == '\\' ? backslash(s) : (unsigned char)*s->str++; + if (stopval < (unsigned char)s->lastch) { + s->str = savestart; + return 0; + } + s->cnt = stopval - s->lastch + 1; + s->state = RANGE; + --s->lastch; + return 1; +} + +static void +genseq(STR *s) +{ + char *ep; + + if (s->which == STRING1) { + errx(1, "Sequences only valid in string2"); + } + + if (*s->str == '\\') { + s->lastch = backslash(s); + } else { + s->lastch = (unsigned char)*s->str++; + } + if (*s->str != '*') { + errx(1, "Misplaced sequence asterisk"); + } + + s->str++; + switch (s->str[0]) { + case '\\': + s->cnt = backslash(s); + break; + case ']': + s->cnt = 0; + ++s->str; + break; + default: + if (isdigit((unsigned char)s->str[0])) { + s->cnt = strtol(s->str, &ep, 0); + if (*ep == ']') { + s->str = ep + 1; + break; + } + } + errx(1, "illegal sequence count"); + /* NOTREACHED */ + } + + s->state = s->cnt ? SEQUENCE : INFINITE; +} + +/* + * Translate \??? into a character. Up to 3 octal digits, if no digits either + * an escape code or a literal character. + */ +static int +backslash(STR *s) +{ + int ch, cnt, val; + + cnt = val = 0; + for (;;) { + /* Consume the character we're already on. */ + s->str++; + + /* Look at the next character. */ + ch = (unsigned char)s->str[0]; + if (!isascii(ch) || !isdigit(ch)) { + break; + } + val = val * 8 + ch - '0'; + if (++cnt == 3) { + /* Enough digits; consume this one and stop */ + ++s->str; + break; + } + } + if (cnt) { + /* We saw digits, so return their value */ + if (val >= OOBCH) + errx(1, "Invalid octal character value"); + return val; + } + if (ch == '\0') { + /* \ -> \ */ + s->state = EOS; + return '\\'; + } + + /* Consume the escaped character */ + s->str++; + + switch (ch) { + case 'a': /* escape characters */ + return '\7'; + case 'b': + return '\b'; + case 'e': + return '\033'; + case 'f': + return '\f'; + case 'n': + return '\n'; + case 'r': + return '\r'; + case 't': + return '\t'; + case 'v': + return '\13'; + default: /* \q -> q */ + return ch; + } +} diff --git a/usr.bin/tr/tr.1 b/usr.bin/tr/tr.1 new file mode 100644 index 0000000..fa9d121 --- /dev/null +++ b/usr.bin/tr/tr.1 @@ -0,0 +1,352 @@ +.\" $NetBSD: tr.1,v 1.22 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tr.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd May 29, 2013 +.Dt TR 1 +.Os +.Sh NAME +.Nm tr +.Nd translate characters +.Sh SYNOPSIS +.Nm +.Op Fl cs +.Ar string1 string2 +.Nm +.Op Fl c +.Fl d +.Ar string1 +.Nm +.Op Fl c +.Fl s +.Ar string1 +.Nm +.Op Fl c +.Fl ds +.Ar string1 string2 +.Sh DESCRIPTION +The +.Nm +utility copies the standard input to the standard output with substitution +or deletion of selected characters. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl c +Complements the set of characters in +.Ar string1 ; +that is, +.Fl c Ar \&ab +includes every character except for +.Sq a +and +.Sq b . +.It Fl d +The +.Fl d +option causes characters to be deleted from the input. +.It Fl s +The +.Fl s +option squeezes multiple occurrences of the characters listed in the last +operand (either +.Ar string1 +or +.Ar string2 ) +in the input into a single instance of the character. +This occurs after all deletion and translation is completed. +.El +.Pp +In the first synopsis form, the characters in +.Ar string1 +are translated into the characters in +.Ar string2 +where the first character in +.Ar string1 +is translated into the first character in +.Ar string2 +and so on. +If +.Ar string1 +is longer than +.Ar string2 , +the last character found in +.Ar string2 +is duplicated until +.Ar string1 +is exhausted. +.Pp +In the second synopsis form, the characters in +.Ar string1 +are deleted from the input. +.Pp +In the third synopsis form, the characters in +.Ar string1 +are compressed as described for the +.Fl s +option. +.Pp +In the fourth synopsis form, the characters in +.Ar string1 +are deleted from the input, and the characters in +.Ar string2 +are compressed as described for the +.Fl s +option. +.Pp +The following conventions can be used in +.Ar string1 +and +.Ar string2 +to specify sets of characters: +.Bl -tag -width [:equiv:] +.It character +Any character not described by one of the following conventions +represents itself. +.It \eoctal +A backslash followed by 1, 2 or 3 octal digits represents a character +with that encoded value. +To follow an octal sequence with a digit as a character, left zero-pad +the octal sequence to the full 3 octal digits. +.It \echaracter +A backslash followed by certain special characters maps to special +values. +.sp +.Bl -column cc +.It \ea +.It \eb +.It \ef +.It \en +.It \er +.It \et +.It \ev +.El +.sp +A backslash followed by any other character maps to that character. +.It c-c +Represents the range of characters between the range endpoints, inclusively. +.It [:class:] +Represents all characters belonging to the defined character class. +Class names are: +.sp +.Bl -column xdigit +.It alnum +.It alpha +.It blank +.It cntrl +.It digit +.It graph +.It lower +.It print +.It punct +.It space +.It upper +.It xdigit +.El +.Pp +.\" All classes may be used in +.\" .Ar string1 , +.\" and in +.\" .Ar string2 +.\" when both the +.\" .Fl d +.\" and +.\" .Fl s +.\" options are specified. +.\" Otherwise, only the classes ``upper'' and ``lower'' may be used in +.\" .Ar string2 +.\" and then only when the corresponding class (``upper'' for ``lower'' +.\" and vice-versa) is specified in the same relative position in +.\" .Ar string1 . +.\" .Pp +With the exception of the +.Dq upper +and +.Dq lower +classes, characters in the classes are in unspecified order. +In the +.Dq upper +and +.Dq lower +classes, characters are entered in ascending order. +.Pp +For specific information as to which ASCII characters are included +in these classes, see +.Xr ctype 3 +and related manual pages. +.It [=equiv=] +Represents all characters or collating (sorting) elements belonging to +the same equivalence class as +.Ar equiv . +If there is a secondary ordering within the equivalence class, the +characters are ordered in ascending sequence. +Otherwise, they are ordered after their encoded values. +An example of an equivalence class might be +.Dq \&c +and +.Dq \&ch +in Spanish; +English has no equivalence classes. +.It [#*n] +Represents +.Ar n +repeated occurrences of the character represented by +.Ar # . +This +expression is only valid when it occurs in +.Ar string2 . +If +.Ar n +is omitted or is zero, it is interpreted as large enough to extend the +.Ar string2 +sequence to the length of +.Ar string1 . +If +.Ar n +has a leading zero, it is interpreted as an octal value; +otherwise, it is interpreted as a decimal value. +.El +.Sh EXIT STATUS +.Ex -std +.Sh EXAMPLES +The following examples are shown as given to the shell: +.Pp +Create a list of the words in +.Ar file1 , +one per line, where a word is taken to be a maximal string of letters: +.sp +.D1 Li "tr -cs \*q[:alpha:]\*q \*q\en\*q < file1" +.sp +Translate the contents of +.Ar file1 +to upper-case: +.sp +.D1 Li "tr \*q[:lower:]\*q \*q[:upper:]\*q < file1" +.sp +Strip out non-printable characters from +.Ar file1 : +.sp +.D1 Li "tr -cd \*q[:print:]\*q < file1" +.Sh COMPATIBILITY +.At V +has historically implemented character ranges using the syntax +.Dq [c-c] +instead of the +.Dq c-c +used by historic +.Bx +implementations and standardized by POSIX. +.At V +shell scripts should work under this implementation as long as +the range is intended to map in another range, i.e. the command +.Pp +.Ic "tr [a-z] [A-Z]" +.Pp +will work as it will map the +.Sq \&[ +character in +.Ar string1 +to the +.Sq \&[ +character in +.Ar string2 . +However, if the shell script is deleting or squeezing characters as in +the command +.Pp +.Ic "tr -d [a-z]" +.Pp +the characters +.Sq \&[ +and +.Sq \&] +will be included in the deletion or compression list which would +not have happened under an historic +.At V +implementation. +Additionally, any scripts that depended on the sequence +.Dq a-z +to represent the three characters +.Sq \&a , +.Sq \&- , +and +.Sq \&z +will have to be rewritten as +.Dq a\e-z . +.Pp +The +.Nm +utility has historically not permitted the manipulation of NUL bytes in +its input and, additionally, stripped NUL's from its input stream. +This implementation has removed this behavior as a bug. +.Pp +The +.Nm +utility has historically been extremely forgiving of syntax errors, +for example, the +.Fl c +and +.Fl s +options were ignored unless two strings were specified. +This implementation will not permit illegal syntax. +.Sh SEE ALSO +.Xr dd 1 , +.Xr sed 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. +It should be noted that the feature wherein the last character of +.Ar string2 +is duplicated if +.Ar string2 +has less characters than +.Ar string1 +is permitted by POSIX but is not required. +Shell scripts attempting to be portable to other POSIX systems should use +the +.Dq [#*n] +convention instead of relying on this behavior. +.Sh BUGS +.Nm +was originally designed to work with +.Tn US-ASCII . +Its use with character sets that do not share all the properties of +.Tn US-ASCII , +e.g., a symmetric set of upper and lower case characters +that can be algorithmically converted one to the other, +may yield unpredictable results. +.Pp +.Nm +should be internationalized. diff --git a/usr.bin/tr/tr.c b/usr.bin/tr/tr.c new file mode 100644 index 0000000..8221880 --- /dev/null +++ b/usr.bin/tr/tr.c @@ -0,0 +1,283 @@ +/* $NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $ */ + +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tr.c 8.2 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: tr.c,v 1.20 2013/08/11 01:54:35 dholland Exp $"); +#endif /* not lint */ + +#include + +#include +#include +#include +#include +#include + +#include "extern.h" + +static int string1[NCHARS], string2[NCHARS]; + +static void setup(int *, const char *, int, int); +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + int ch, ch2, lastch; + int cflag, dflag, sflag, isstring2; + STR *s1, *s2; + + cflag = dflag = sflag = 0; + while ((ch = getopt(argc, argv, "cds")) != -1) + switch (ch) { + case 'c': + cflag = 1; + break; + case 'd': + dflag = 1; + break; + case 's': + sflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + switch(argc) { + case 0: + default: + usage(); + /* NOTREACHED */ + case 1: + isstring2 = 0; + break; + case 2: + isstring2 = 1; + break; + } + + /* + * tr -ds [-c] string1 string2 + * Delete all characters (or complemented characters) in string1. + * Squeeze all characters in string2. + */ + if (dflag && sflag) { + if (!isstring2) + usage(); + + setup(string1, argv[0], 1, cflag); + setup(string2, argv[1], 2, 0); + + for (lastch = OOBCH; (ch = getchar()) != EOF; ) + if (!string1[ch] && (!string2[ch] || lastch != ch)) { + lastch = ch; + (void)putchar(ch); + } + exit(0); + } + + /* + * tr -d [-c] string1 + * Delete all characters (or complemented characters) in string1. + */ + if (dflag) { + if (isstring2) + usage(); + + setup(string1, argv[0], 1, cflag); + + while ((ch = getchar()) != EOF) + if (!string1[ch]) + (void)putchar(ch); + exit(0); + } + + /* + * tr -s [-c] string1 + * Squeeze all characters (or complemented characters) in string1. + */ + if (sflag && !isstring2) { + setup(string1, argv[0], 1, cflag); + + for (lastch = OOBCH; (ch = getchar()) != EOF;) + if (!string1[ch] || lastch != ch) { + lastch = ch; + (void)putchar(ch); + } + exit(0); + } + + /* + * tr [-cs] string1 string2 + * Replace all characters (or complemented characters) in string1 with + * the character in the same position in string2. If the -s option is + * specified, squeeze all the characters in string2. + */ + if (!isstring2) + usage(); + + /* + * The first and second strings need to be matched up. This + * means that if we are doing -c, we need to scan the first + * string in advance, complement it, and match *that* against + * the second string; otherwise we need to scan them together. + */ + + if (cflag) { + /* + * Scan string 1 and complement it. After this, + * string1[] contains 0 for chars to leave alone and 1 + * for chars to translate. + */ + setup(string1, argv[0], 1, cflag); + s1 = NULL; /* for safety */ + /* we will use ch to iterate over string1, so start it */ + ch = -1; + } else { + /* Create the scanner for string 1. */ + s1 = str_create(1, argv[0]); + for (ch = 0; ch < NCHARS; ch++) { + string1[ch] = ch; + } + } + /* Create the scanner for string 2. */ + s2 = str_create(2, argv[1]); + + /* Read the first char of string 2 first to make sure there is one. */ + if (!next(s2, &ch2)) + errx(1, "empty string2"); + + /* + * Loop over the chars from string 1. After this loop string1[] + * is a mapping from input to output chars. + */ + while (1) { + if (cflag) { + /* + * Try each character in order. For characters we + * skip over because we aren't translating them, + * set the translation to the identity. + */ + ch++; + while (ch < NCHARS && string1[ch] == 0) { + if (string1[ch] == 0) { + string1[ch] = ch; + } + ch++; + } + if (ch == NCHARS) { + break; + } + } + else { + /* Get the next character from string 1. */ + if (!next(s1, &ch)) { + break; + } + } + + /* Set the translation to the character from string 2. */ + string1[ch] = ch2; + + /* Note the characters to squeeze in string2[]. */ + if (sflag) { + string2[ch2] = 1; + } + + /* + * Get the next character from string 2. If it runs + * out, this will keep returning the last character + * over and over again. + */ + (void)next(s2, &ch2); + } + + /* + * Now do it. + */ + + if (sflag) + for (lastch = OOBCH; (ch = getchar()) != EOF;) { + ch = string1[ch]; + if (!string2[ch] || lastch != ch) { + lastch = ch; + (void)putchar(ch); + } + } + else + while ((ch = getchar()) != EOF) + (void)putchar(string1[ch]); + + /* Clean up and exit. */ + if (s1 != NULL) { + str_destroy(s1); + } + str_destroy(s2); + exit (0); +} + +static void +setup(int *string, const char *arg, int whichstring, int cflag) +{ + int cnt, *p; + int ch; + STR *str; + + str = str_create(whichstring, arg); + while (next(str, &ch)) + string[ch] = 1; + if (cflag) + for (p = string, cnt = NCHARS; cnt--; ++p) + *p = !*p; + str_destroy(str); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: tr [-cs] string1 string2\n"); + (void)fprintf(stderr, " tr [-c] -d string1\n"); + (void)fprintf(stderr, " tr [-c] -s string1\n"); + (void)fprintf(stderr, " tr [-c] -ds string1 string2\n"); + exit(1); +} diff --git a/usr.bin/true/true.1 b/usr.bin/true/true.1 new file mode 100644 index 0000000..5829560 --- /dev/null +++ b/usr.bin/true/true.1 @@ -0,0 +1,56 @@ +.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $ +.\" +.\" Copyright (c) 1983, 1985, 1990 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)true.1 6.4 (Berkeley) 6/27/91 +.\" $NetBSD: true.1,v 1.7 2003/08/07 11:16:48 agc Exp $ +.\" +.Dd June 27, 1991 +.Dt TRUE 1 +.Os +.Sh NAME +.Nm true +.Nd return true value +.Sh SYNOPSIS +.Nm +.Sh DESCRIPTION +The +.Nm +utility always returns with exit code zero. +.Sh SEE ALSO +.Xr csh 1 , +.Xr false 1 , +.Xr sh 1 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/true/true.sh b/usr.bin/true/true.sh new file mode 100644 index 0000000..20f87ef --- /dev/null +++ b/usr.bin/true/true.sh @@ -0,0 +1,2 @@ +#! /bin/sh +exit 0 diff --git a/usr.bin/tsort/tsort.1 b/usr.bin/tsort/tsort.1 new file mode 100644 index 0000000..061824b --- /dev/null +++ b/usr.bin/tsort/tsort.1 @@ -0,0 +1,87 @@ +.\" $NetBSD: tsort.1,v 1.10 2003/08/07 11:16:50 agc Exp $ +.\" +.\" Copyright (c) 1990, 1993, 1994 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This manual is derived from one contributed to Berkeley by +.\" Michael Rendell of Memorial University of Newfoundland. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tsort.1 8.3 (Berkeley) 4/1/94 +.\" +.Dd April 1, 1994 +.Dt TSORT 1 +.Os +.Sh NAME +.Nm tsort +.Nd topological sort of a directed graph +.Sh SYNOPSIS +.Nm +.Op Fl l +.Op Fl q +.Op Ar file +.Sh DESCRIPTION +.Nm +takes a list of pairs of node names representing directed arcs in +a graph and prints the nodes in topological order on standard output. +Input is taken from the named +.Ar file , +or from standard input if no file +is given. +.Pp +Node names in the input are separated by white space and there must +be an even number of node names. +.Pp +Presence of a node in a graph can be represented by an arc from the node +to itself. +This is useful when a node is not connected to any other nodes. +.Pp +If the graph contains a cycle (and therefore cannot be properly sorted), +one of the arcs in the cycle is ignored and the sort continues. +Cycles are reported on standard error. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl l +Search for and display the longest cycle. +Can take a very long time. +.It Fl q +Do not display informational messages about cycles. +This is primarily +intended for building libraries, where optimal ordering is not critical, +and cycles occur often. +.El +.Sh SEE ALSO +.Xr ar 1 +.Sh HISTORY +A +.Nm +command appeared in +.At v7 . +This +.Nm +command and manual page are derived from sources contributed to Berkeley by +Michael Rendell of Memorial University of Newfoundland. diff --git a/usr.bin/tsort/tsort.c b/usr.bin/tsort/tsort.c new file mode 100644 index 0000000..3e8d569 --- /dev/null +++ b/usr.bin/tsort/tsort.c @@ -0,0 +1,433 @@ +/* $NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Rendell of Memorial University of Newfoundland. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1989, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#if 0 +static char sccsid[] = "@(#)tsort.c 8.3 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: tsort.c,v 1.23 2011/09/06 18:34:37 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Topological sort. Input is a list of pairs of strings separated by + * white space (spaces, tabs, and/or newlines); strings are written to + * standard output in sorted order, one per line. + * + * usage: + * tsort [-l] [inputfile] + * If no input file is specified, standard input is read. + * + * Should be compatible with AT&T tsort HOWEVER the output is not identical + * (i.e. for most graphs there is more than one sorted order, and this tsort + * usually generates a different one then the AT&T tsort). Also, cycle + * reporting seems to be more accurate in this version (the AT&T tsort + * sometimes says a node is in a cycle when it isn't). + * + * Michael Rendell, michael@stretch.cs.mun.ca - Feb 26, '90 + */ +#define HASHSIZE 53 /* doesn't need to be big */ +#define NF_MARK 0x1 /* marker for cycle detection */ +#define NF_ACYCLIC 0x2 /* this node is cycle free */ +#define NF_NODEST 0x4 /* Unreachable */ + +typedef struct node_str NODE; + +struct node_str { + NODE **n_prevp; /* pointer to previous node's n_next */ + NODE *n_next; /* next node in graph */ + NODE **n_arcs; /* array of arcs to other nodes */ + int n_narcs; /* number of arcs in n_arcs[] */ + int n_arcsize; /* size of n_arcs[] array */ + int n_refcnt; /* # of arcs pointing to this node */ + int n_flags; /* NF_* */ + char n_name[1]; /* name of this node */ +}; + +typedef struct _buf { + char *b_buf; + int b_bsize; +} BUF; + +static DB *db; +static NODE *graph, **cycle_buf, **longest_cycle; +static int debug, longest, quiet; + +static void add_arc(char *, char *); +static void clear_cycle(void); +static int find_cycle(NODE *, NODE *, int, int); +static NODE *get_node(char *); +static void *grow_buf(void *, int); +static void remove_node(NODE *); +static void tsort(void); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + BUF *b; + int c, n; + FILE *fp; + int bsize, ch, nused; + BUF bufs[2]; + + setprogname(argv[0]); + + fp = NULL; + while ((ch = getopt(argc, argv, "dlq")) != -1) + switch (ch) { + case 'd': + debug = 1; + break; + case 'l': + longest = 1; + break; + case 'q': + quiet = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + switch (argc) { + case 0: + fp = stdin; + break; + case 1: + if ((fp = fopen(*argv, "r")) == NULL) + err(1, "%s", *argv); + break; + default: + usage(); + } + + for (b = bufs, n = 2; --n >= 0; b++) + b->b_buf = grow_buf(NULL, b->b_bsize = 1024); + + /* parse input and build the graph */ + for (n = 0, c = getc(fp);;) { + while (c != EOF && isspace(c)) + c = getc(fp); + if (c == EOF) + break; + + nused = 0; + b = &bufs[n]; + bsize = b->b_bsize; + do { + b->b_buf[nused++] = c; + if (nused == bsize) + b->b_buf = grow_buf(b->b_buf, bsize *= 2); + c = getc(fp); + } while (c != EOF && !isspace(c)); + + b->b_buf[nused] = '\0'; + b->b_bsize = bsize; + if (n) + add_arc(bufs[0].b_buf, bufs[1].b_buf); + n = !n; + } + (void)fclose(fp); + if (n) + errx(1, "odd data count"); + + /* do the sort */ + tsort(); + return(0); +} + +/* double the size of oldbuf and return a pointer to the new buffer. */ +static void * +grow_buf(void *bp, int size) +{ + void *n; + + if ((n = realloc(bp, (u_int)size)) == NULL) + err(1, "realloc"); + bp = n; + return (bp); +} + +/* + * add an arc from node s1 to node s2 in the graph. If s1 or s2 are not in + * the graph, then add them. + */ +static void +add_arc(char *s1, char *s2) +{ + NODE *n1; + NODE *n2; + int bsize, i; + + n1 = get_node(s1); + + if (!strcmp(s1, s2)) + return; + + n2 = get_node(s2); + + /* + * Check if this arc is already here. + */ + for (i = 0; i < n1->n_narcs; i++) + if (n1->n_arcs[i] == n2) + return; + /* + * Add it. + */ + if (n1->n_narcs == n1->n_arcsize) { + if (!n1->n_arcsize) + n1->n_arcsize = 10; + bsize = n1->n_arcsize * sizeof(*n1->n_arcs) * 2; + n1->n_arcs = grow_buf(n1->n_arcs, bsize); + n1->n_arcsize = bsize / sizeof(*n1->n_arcs); + } + n1->n_arcs[n1->n_narcs++] = n2; + ++n2->n_refcnt; +} + +/* Find a node in the graph (insert if not found) and return a pointer to it. */ +static NODE * +get_node(char *name) +{ + DBT data, key; + NODE *n; + + if (db == NULL && + (db = dbopen(NULL, O_RDWR, 0, DB_HASH, NULL)) == NULL) + err(1, "db: %s", name); + + key.data = name; + key.size = strlen(name) + 1; + + switch ((*db->get)(db, &key, &data, 0)) { + case 0: + (void)memmove(&n, data.data, sizeof(n)); + return (n); + case 1: + break; + default: + case -1: + err(1, "db: %s", name); + } + + if ((n = malloc(sizeof(NODE) + key.size)) == NULL) + err(1, "malloc"); + + n->n_narcs = 0; + n->n_arcsize = 0; + n->n_arcs = NULL; + n->n_refcnt = 0; + n->n_flags = 0; + (void)memmove(n->n_name, name, key.size); + + /* Add to linked list. */ + if ((n->n_next = graph) != NULL) + graph->n_prevp = &n->n_next; + n->n_prevp = &graph; + graph = n; + + /* Add to hash table. */ + data.data = &n; + data.size = sizeof(n); + if ((*db->put)(db, &key, &data, 0)) + err(1, "db: %s", name); + return (n); +} + + +/* + * Clear the NODEST flag from all nodes. + */ +static void +clear_cycle(void) +{ + NODE *n; + + for (n = graph; n != NULL; n = n->n_next) + n->n_flags &= ~NF_NODEST; +} + +/* do topological sort on graph */ +static void +tsort(void) +{ + NODE *n, *next; + int cnt, i; + + while (graph != NULL) { + /* + * Keep getting rid of simple cases until there are none left, + * if there are any nodes still in the graph, then there is + * a cycle in it. + */ + do { + for (cnt = 0, n = graph; n != NULL; n = next) { + next = n->n_next; + if (n->n_refcnt == 0) { + remove_node(n); + ++cnt; + } + } + } while (graph != NULL && cnt); + + if (graph == NULL) + break; + + if (!cycle_buf) { + /* + * Allocate space for two cycle logs - one to be used + * as scratch space, the other to save the longest + * cycle. + */ + for (cnt = 0, n = graph; n != NULL; n = n->n_next) + ++cnt; + cycle_buf = malloc((u_int)sizeof(NODE *) * cnt); + longest_cycle = malloc((u_int)sizeof(NODE *) * cnt); + if (cycle_buf == NULL || longest_cycle == NULL) + err(1, "malloc"); + } + for (n = graph; n != NULL; n = n->n_next) { + if (!(n->n_flags & NF_ACYCLIC)) { + if ((cnt = find_cycle(n, n, 0, 0)) != 0) { + if (!quiet) { + warnx("cycle in data"); + for (i = 0; i < cnt; i++) + warnx("%s", + longest_cycle[i]->n_name); + } + remove_node(n); + clear_cycle(); + break; + } else { + /* to avoid further checks */ + n->n_flags |= NF_ACYCLIC; + clear_cycle(); + } + } + } + if (n == NULL) + errx(1, "internal error -- could not find cycle"); + } +} + +/* print node and remove from graph (does not actually free node) */ +static void +remove_node(NODE *n) +{ + NODE **np; + int i; + + (void)printf("%s\n", n->n_name); + for (np = n->n_arcs, i = n->n_narcs; --i >= 0; np++) + --(*np)->n_refcnt; + n->n_narcs = 0; + *n->n_prevp = n->n_next; + if (n->n_next) + n->n_next->n_prevp = n->n_prevp; +} + + +/* look for the longest? cycle from node from to node to. */ +static int +find_cycle(NODE *from, NODE *to, int longest_len, int depth) +{ + NODE **np; + int i, len; + + /* + * avoid infinite loops and ignore portions of the graph known + * to be acyclic + */ + if (from->n_flags & (NF_NODEST|NF_MARK|NF_ACYCLIC)) + return (0); + from->n_flags |= NF_MARK; + + for (np = from->n_arcs, i = from->n_narcs; --i >= 0; np++) { + cycle_buf[depth] = *np; + if (*np == to) { + if (depth + 1 > longest_len) { + longest_len = depth + 1; + (void)memcpy(longest_cycle, cycle_buf, + longest_len * sizeof(NODE *)); + } + } else { + if ((*np)->n_flags & (NF_MARK|NF_ACYCLIC|NF_NODEST)) + continue; + len = find_cycle(*np, to, longest_len, depth + 1); + + if (debug) + (void)printf("%*s %s->%s %d\n", depth, "", + from->n_name, to->n_name, len); + + if (len == 0) + (*np)->n_flags |= NF_NODEST; + + if (len > longest_len) + longest_len = len; + + if (len > 0 && !longest) + break; + } + } + from->n_flags &= ~NF_MARK; + return (longest_len); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: tsort [-lq] [file]\n"); + exit(1); +} diff --git a/usr.bin/tty/tty.1 b/usr.bin/tty/tty.1 new file mode 100644 index 0000000..5b65d66 --- /dev/null +++ b/usr.bin/tty/tty.1 @@ -0,0 +1,75 @@ +.\" $NetBSD: tty.1,v 1.9 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tty.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt TTY 1 +.Os +.Sh NAME +.Nm tty +.Nd return user's terminal name +.Sh SYNOPSIS +.Nm +.Op Fl s +.Sh DESCRIPTION +The +.Nm +utility writes the name of the terminal attached to standard input +to standard output. +The name that is written is the string returned by +.Xr ttyname 3 . +If the standard input is not a terminal, the message ``not a tty'' +is written. +The options are as follows: +.Bl -tag -width Ds +.It Fl s +Don't write the terminal name; only the exit status is affected +when this option is specified. +The +.Fl s +option is deprecated in favor of the +.Dq Li "test -t 0" +command. +.El +.Pp +.Nm +exits 0 if the standard input is a terminal, 1 if the standard input is +not a terminal, and >1 if an error occurs. +.Sh SEE ALSO +.Xr test 1 , +.Xr ttyname 3 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/tty/tty.c b/usr.bin/tty/tty.c new file mode 100644 index 0000000..c5e5e80 --- /dev/null +++ b/usr.bin/tty/tty.c @@ -0,0 +1,81 @@ +/* $NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $ */ + +/* + * Copyright (c) 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)tty.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: tty.c,v 1.8 2011/09/06 18:34:57 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char **argv) +{ + int ch, sflag; + char *t; + + sflag = 0; + while ((ch = getopt(argc, argv, "s")) != -1) { + switch((char)ch) { + case 's': + sflag = 1; + break; + case '?': + default: + usage(); + /* NOTREACHED */ + } + } + + t = ttyname(STDIN_FILENO); + if (!sflag) + puts(t ? t : "not a tty"); + exit(t ? 0 : 1); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: tty [-s]\n"); + exit(2); +} diff --git a/usr.bin/uname/uname.1 b/usr.bin/uname/uname.1 new file mode 100644 index 0000000..8253ea9 --- /dev/null +++ b/usr.bin/uname/uname.1 @@ -0,0 +1,86 @@ +.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $ +.\" +.\" Copyright (c) 1990 The Regents of the University of California. +.\" All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)du.1 6.13 (Berkeley) 6/20/91 +.\" $NetBSD: uname.1,v 1.12 2005/03/27 18:41:22 peter Exp $ +.\" +.Dd November 9, 1998 +.Dt UNAME 1 +.Os +.Sh NAME +.Nm uname +.Nd Print operating system name +.Sh SYNOPSIS +.Nm +.Op Fl amnprsv +.Sh DESCRIPTION +The +.Nm +utility writes symbols representing one or more system characteristics +to the standard output. +.Pp +The following options are available: +.Bl -tag -width indent +.It Fl a +Behave as though all of the options +.Fl mnrsv +were specified. +.It Fl m +print the machine hardware name. +.It Fl n +print the nodename (the nodename may be a name +that the system is known by to a communications +network). +.It Fl p +print the machine processor architecture name. +.It Fl r +print the operating system release. +.It Fl s +print the operating system name. +.It Fl v +print the operating system version. +.El +.Pp +If no options are specified, +.Nm +prints the operating system name as if the +.Fl s +option had been specified. +.Sh SEE ALSO +.Xr hostname 1 , +.Xr machine 1 , +.Xr uname 3 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . +The +.Fl p +option is an extension to the standard. diff --git a/usr.bin/uname/uname.c b/usr.bin/uname/uname.c new file mode 100644 index 0000000..de5e08e --- /dev/null +++ b/usr.bin/uname/uname.c @@ -0,0 +1,159 @@ +/* $NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $ */ + +/* + * Copyright (c) 1994 Winning Strategies, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Winning Strategies, Inc. + * 4. The name of Winning Strategies, Inc. may not be used to endorse or + * promote products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: uname.c,v 1.11 2011/09/06 18:35:13 joerg Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +__dead static void usage(void); + +/* Note that PRINT_MACHINE_ARCH is excluded from PRINT_ALL! */ +#define PRINT_SYSNAME 0x01 +#define PRINT_NODENAME 0x02 +#define PRINT_RELEASE 0x04 +#define PRINT_VERSION 0x08 +#define PRINT_MACHINE 0x10 +#define PRINT_MACHINE_ARCH 0x20 +#define PRINT_ALL \ + (PRINT_SYSNAME|PRINT_NODENAME|PRINT_RELEASE|PRINT_VERSION|PRINT_MACHINE) + +int +main(int argc, char **argv) +{ + struct utsname u; + char machine_arch[SYS_NMLN]; + int c; + int space = 0; + int print_mask = 0; + + (void)setlocale(LC_ALL, ""); + + while ((c = getopt(argc,argv,"amnprsv")) != -1) { + switch (c) { + case 'a': + print_mask |= PRINT_ALL; + break; + case 'm': + print_mask |= PRINT_MACHINE; + break; + case 'n': + print_mask |= PRINT_NODENAME; + break; + case 'p': + print_mask |= PRINT_MACHINE_ARCH; + break; + case 'r': + print_mask |= PRINT_RELEASE; + break; + case 's': + print_mask |= PRINT_SYSNAME; + break; + case 'v': + print_mask |= PRINT_VERSION; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + if (optind != argc) { + usage(); + /* NOTREACHED */ + } + + if (!print_mask) { + print_mask = PRINT_SYSNAME; + } + + if (uname(&u) != 0) { + err(EXIT_FAILURE, "uname"); + /* NOTREACHED */ + } + if (print_mask & PRINT_MACHINE_ARCH) { + int mib[2] = { CTL_HW, HW_MACHINE_ARCH }; + size_t len = sizeof (machine_arch); + + if (sysctl(mib, sizeof (mib) / sizeof (mib[0]), machine_arch, + &len, NULL, 0) < 0) + err(EXIT_FAILURE, "sysctl"); + } + + if (print_mask & PRINT_SYSNAME) { + space++; + fputs(u.sysname, stdout); + } + if (print_mask & PRINT_NODENAME) { + if (space++) putchar(' '); + fputs(u.nodename, stdout); + } + if (print_mask & PRINT_RELEASE) { + if (space++) putchar(' '); + fputs(u.release, stdout); + } + if (print_mask & PRINT_VERSION) { + if (space++) putchar(' '); + fputs(u.version, stdout); + } + if (print_mask & PRINT_MACHINE) { + if (space++) putchar(' '); + fputs(u.machine, stdout); + } + if (print_mask & PRINT_MACHINE_ARCH) { + if (space++) putchar(' '); + fputs(machine_arch, stdout); + } + putchar('\n'); + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +static void +usage(void) +{ + fprintf(stderr, "usage: uname [-amnprsv]\n"); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/unexpand/unexpand.c b/usr.bin/unexpand/unexpand.c new file mode 100644 index 0000000..a9d7279 --- /dev/null +++ b/usr.bin/unexpand/unexpand.c @@ -0,0 +1,218 @@ +/* $NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $ */ + +/*- + * Copyright (c) 1980, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)unexpand.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: unexpand.c,v 1.15 2016/02/03 05:32:14 christos Exp $"); +#endif /* not lint */ + +/* + * unexpand - put tabs into a file replacing blanks + */ +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DSTOP 8 +static int all; +static size_t nstops; +static size_t maxstops; +static size_t *tabstops; + +static void tabify(const char *, size_t); +static void usage(void) __attribute__((__noreturn__)); + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-a] [-t tabstop] [file ...]\n", + getprogname()); + exit(EXIT_FAILURE); +} + +int +main(int argc, char **argv) +{ + int c; + char *ep, *tab; + char *line; + size_t len; + unsigned long i; + + setprogname(argv[0]); + + while ((c = getopt(argc, argv, "at:")) != -1) { + switch (c) { + case 'a': + if (nstops) + usage(); + all++; + break; + case 't': + if (all) + usage(); + while ((tab = strsep(&optarg, ", \t")) != NULL) { + if (*tab == '\0') + continue; + errno = 0; + i = strtoul(tab, &ep, 0); + if (*ep || (errno == ERANGE && i == ULONG_MAX)) + errx(EXIT_FAILURE, + "Invalid tabstop `%s'", tab); + if (nstops >= maxstops) { + maxstops += 20; + tabstops = erealloc(tabstops, maxstops); + } + if (nstops && tabstops[nstops - 1] >= (size_t)i) + errx(EXIT_FAILURE, + "Bad tabstop spec `%s', must be " + "greater than the previous `%zu'", + tab, tabstops[nstops - 1]); + tabstops[nstops++] = i; + } + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + for (i = 0; i < nstops; i++) + fprintf(stderr, "%lu %zu\n", i, tabstops[i]); + + do { + if (argc > 0) { + if (freopen(argv[0], "r", stdin) == NULL) + err(EXIT_FAILURE, "Cannot open `%s'", argv[0]); + argc--, argv++; + } + while ((line = fgetln(stdin, &len)) != NULL) + tabify(line, len); + } while (argc > 0); + return EXIT_SUCCESS; +} + +static void +tabify(const char *line, size_t len) +{ + const char *e, *p; + size_t dcol, ocol, limit, n; + + dcol = ocol = 0; + limit = nstops == 0 ? UINT_MAX : tabstops[nstops - 1] - 1; + e = line + len; + for (p = line; p < e; p++) { + if (*p == ' ') { + dcol++; + continue; + } else if (*p == '\t') { + if (nstops == 0) { + dcol = (1 + dcol / DSTOP) * DSTOP; + continue; + } else { + for (n = 0; n < nstops && + tabstops[n] - 1 < dcol; n++) + continue; + if (n < nstops - 1 && tabstops[n] - 1 < limit) { + dcol = tabstops[n]; + continue; + } + } + } + + /* Output our tabs */ + if (nstops == 0) { + while (((ocol + DSTOP) / DSTOP) <= (dcol / DSTOP)) { + if (dcol - ocol < 2) + break; + if (putchar('\t') == EOF) + goto out; + ocol = (1 + ocol / DSTOP) * DSTOP; + } + } else { + for (n = 0; n < nstops && tabstops[n] <= ocol; n++) + continue; + while (n < nstops && tabstops[n] <= dcol && ocol < dcol + && ocol < limit) { + if (putchar('\t') == EOF) + goto out; + ocol = tabstops[n++]; + } + } + + /* Output remaining spaces */ + while (ocol < dcol && ocol < limit) { + if (putchar(' ') == EOF) + goto out; + ocol++; + } + + /* Output our char */ + if (putchar(*p) == EOF) + goto out; + if (*p == '\b') { + if (ocol > 0) { + ocol--; + dcol--; + } + } else { + ocol++; + dcol++; + } + + /* Output remainder of line */ + if (!all || dcol >= limit) { + for (p++; p < e; p++) + if (putchar(*p) == EOF) + goto out; + return; + } + } + return; +out: + err(EXIT_FAILURE, "write failed"); +} diff --git a/usr.bin/unifdef/unifdef.1 b/usr.bin/unifdef/unifdef.1 new file mode 100644 index 0000000..7319245 --- /dev/null +++ b/usr.bin/unifdef/unifdef.1 @@ -0,0 +1,347 @@ +.\" $NetBSD: unifdef.1,v 1.13 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1985, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Dave Yost. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" Portions of this code (support for #if and #elif) are Copyright (c) +.\" 2002, 2003 Tony Finch. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. All advertising materials mentioning features or use of this software +.\" must display the following acknowledgement: +.\" This product includes software developed by the University of +.\" California, Berkeley and its contributors. +.\" 4. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)unifdef.1 8.2 (Berkeley) 4/1/94 +.\" $dotat: things/unifdef.1,v 1.45 2003/01/20 14:37:08 fanf2 Exp $ +.\" $FreeBSD: src/usr.bin/unifdef/unifdef.1,v 1.19 2003/01/20 12:41:41 fanf Exp $ +.\" +.Dd June 5, 2009 +.Dt UNIFDEF 1 +.Os +.Sh NAME +.Nm unifdef , +.Nm unifdefall +.Nd remove preprocessor conditionals from code +.Sh SYNOPSIS +.Nm +.Op Fl ceklst +.Op Fl I Ns Ar path +.Op Fl D Ns Ar sym Ns Op = Ns Ar val +.Op Fl U Ns Ar sym +.Op Fl iD Ns Ar sym Ns Op = Ns Ar val +.Op Fl iU Ns Ar sym +.Ar ... +.Op Fl o Ar output +.Op Ar file +.Nm unifdefall +.Op Fl I Ns Ar path +.Ar ... +.Ar file +.Sh DESCRIPTION +The +.Nm +utility selectively processes conditional +.Xr cpp 1 +directives. +It removes from a file both the directives and any additional text +that they specify should be removed, while otherwise leaving the +file alone. +.Pp +The +.Nm +utility acts on +.Ic #if , #ifdef , #ifndef , #elif , #else , +and +.Ic #endif +lines, +and it understands only the commonly-used subset +of the expression syntax for +.Ic #if +and +.Ic #elif +lines. +It handles +integer values of symbols defined on the command line, +the +.Fn defined +operator applied to symbols defined or undefined on the command line, +the operators +.Ic \&! , < , > , <= , >= , == , != , && , || , +and parenthesized expressions. +Anything that it does not understand is passed through unharmed. +It only processes +.Ic #ifdef +and +.Ic #ifndef +directives if the symbol is specified on the command line, +otherwise they are also passed through unchanged. +By default, it ignores +.Ic #if +and +.Ic #elif +lines with constant expressions, +or they may be processed by specifying the +.Fl k +flag on the command line. +.Pp +The +.Nm +utility also understands just enough about C +to know when one of the directives is inactive +because it is inside +a comment, +or affected by a backslash-continued line. +It spots unusually-formatted preprocessor directives +and knows when the layout is too odd to handle. +.Pp +A script called +.Nm unifdefall +can be used to remove all conditional +.Xr cpp 1 +directives from a file. +It uses +.Nm Fl s +and +.Nm cpp Fl dM +to get lists of all the controlling symbols +and their definitions (or lack thereof), +then invokes +.Nm +with appropriate arguments to process the file. +.Pp +Available options: +.Bl -tag -width indent -compact +.It Fl D Ns Ar sym Ns Op = Ns Ar val +Specify that a symbol is defined, +and optionally specify what value to give it +for the purpose of handling +.Ic #if +and +.Ic #elif +directives. +.It Fl U Ns Ar sym +Specify that a symbol is undefined. +If the same symbol appears in more than one argument, +the last occurrence dominates. +.It Fl c +If the +.Fl c +flag is specified, +then the operation of +.Nm +is complemented, +i.e., the lines that would have been removed or blanked +are retained and vice versa. +.It Fl e +Because +.Nm +processes its input one line at a time, +it cannot remove preprocessor directives that span more than one line. +The most common example of this is a directive with a multi-line +comment hanging off its right hand end. +By default, +if +.Nm +has to process such a directive, +it will complain that the line is too obfuscated. +The +.Fl e +option changes the behaviour so that, +where possible, +such lines are left unprocessed instead of reporting an error. +.It Fl k +Process +.Ic #if +and +.Ic #elif +lines with constant expressions. +By default, sections controlled by such lines are passed through unchanged +because they typically start +.Dq Li "#if 0" +and are used as a kind of comment to sketch out future or past development. +It would be rude to strip them out, just as it would be for normal comments. +.It Fl l +Replace removed lines with blank lines +instead of deleting them. +.It Fl o Ar output +The argument given is the name of an +.Ar output +file to be used instead of the standard output. +This file can be the same as the input file. +.It Fl s +Instead of processing the input file as usual, +this option causes +.Nm +to produce a list of symbols that appear in expressions +that +.Nm +understands. +It is useful in conjunction with the +.Fl dM +option of +.Xr cpp 1 +for creating +.Nm +command lines. +.It Fl t +Disables parsing for C comments +and line continuations, +which is useful +for plain text. +.It Fl iD Ns Ar sym Ns Op = Ns Ar val +.It Fl iU Ns Ar sym +Ignore +.Ic #ifdef Ns s . +If your C code uses +.Ic #ifdef Ns s +to delimit non-C lines, +such as comments +or code which is under construction, +then you must tell +.Nm +which symbols are used for that purpose so that it will not try to parse +comments +and line continuations +inside those +.Ic #ifdef Ns s . +One specifies ignored symbols with +.Fl iD Ns Ar sym Ns Oo = Ns Ar val Oc +and +.Fl iU Ns Ar sym +similar to +.Fl D Ns Ar sym Ns Op = Ns Ar val +and +.Fl U Ns Ar sym +above. +.It Fl I Ns Ar path +Specifies to +.Nm unifdefall +an additional place to look for +.Ic #include +files. +This option is ignored by +.Nm +for compatibility with +.Xr cpp 1 +and to simplify the implementation of +.Nm unifdefall . +.El +.Pp +The +.Nm +utility copies its output to +.Em stdout +and will take its input from +.Em stdin +if no +.Ar file +argument is given. +.Pp +The +.Nm +utility works nicely with the +.Fl D Ns Ar sym +option of +.Xr diff 1 . +.Sh DIAGNOSTICS +.Bl -item +.It +Too many levels of nesting. +.It +Inappropriate +.Ic #elif , +.Ic #else +or +.Ic #endif . +.It +Obfuscated preprocessor control line. +.It +Premature +.Dv EOF +(with the line number of the most recent unterminated +.Ic #if ) . +.It +.Dv EOF +in comment. +.El +.Pp +The +.Nm +utility exits 0 if the output is an exact copy of the input, +1 if not, and 2 if in trouble. +.Sh SEE ALSO +.Xr cpp 1 , +.Xr diff 1 +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 . +.Tn ANSI\~C +support was added in +.Fx 4.7 . +.Sh BUGS +Expression evaluation is very limited. +.Pp +Preprocessor control lines split across more than one physical line +(because of comments or backslash-newline) +cannot be handled in every situation. +.Pp +Trigraphs are not recognized. +.Pp +There is no support for symbols with different definitions at +different points in the source file. +.Pp +The text-mode and ignore functionality does not correspond to modern +.Xr cpp 1 +behaviour. diff --git a/usr.bin/unifdef/unifdef.c b/usr.bin/unifdef/unifdef.c new file mode 100644 index 0000000..702e3ec --- /dev/null +++ b/usr.bin/unifdef/unifdef.c @@ -0,0 +1,1037 @@ +/* $NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $ */ + +/* + * Copyright (c) 1985, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Dave Yost. It was rewritten to support ANSI C by Tony Finch. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Copyright (c) 2002, 2003 Tony Finch + * + * This code is derived from software contributed to Berkeley by + * Dave Yost. It was rewritten to support ANSI C by Tony Finch. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#ifndef lint +#if 0 +static const char copyright[] = +"@(#) Copyright (c) 1985, 1993\n\ + The Regents of the University of California. All rights reserved.\n"; +#endif +#ifdef __IDSTRING +__IDSTRING(Berkeley, "@(#)unifdef.c 8.1 (Berkeley) 6/6/93"); +__IDSTRING(NetBSD, "$NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $"); +__IDSTRING(dotat, "$dotat: things/unifdef.c,v 1.161 2003/07/01 15:32:48 fanf2 Exp $"); +#endif +#endif /* not lint */ +#ifdef __FBSDID +__FBSDID("$FreeBSD: src/usr.bin/unifdef/unifdef.c,v 1.18 2003/07/01 15:30:43 fanf Exp $"); +#endif + +/* + * unifdef - remove ifdef'ed lines + * + * Wishlist: + * provide an option which will append the name of the + * appropriate symbol after #else's and #endif's + * provide an option which will check symbols after + * #else's and #endif's to see that they match their + * corresponding #ifdef or #ifndef + * generate #line directives in place of deleted code + * + * The first two items above require better buffer handling, which would + * also make it possible to handle all "dodgy" directives correctly. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "stdbool.h" + +/* types of input lines: */ +typedef enum { + LT_TRUEI, /* a true #if with ignore flag */ + LT_FALSEI, /* a false #if with ignore flag */ + LT_IF, /* an unknown #if */ + LT_TRUE, /* a true #if */ + LT_FALSE, /* a false #if */ + LT_ELIF, /* an unknown #elif */ + LT_ELTRUE, /* a true #elif */ + LT_ELFALSE, /* a false #elif */ + LT_ELSE, /* #else */ + LT_ENDIF, /* #endif */ + LT_DODGY, /* flag: directive is not on one line */ + LT_DODGY_LAST = LT_DODGY + LT_ENDIF, + LT_PLAIN, /* ordinary line */ + LT_EOF, /* end of file */ + LT_COUNT +} Linetype; + +static char const * const linetype_name[] = { + "TRUEI", "FALSEI", "IF", "TRUE", "FALSE", + "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF", + "DODGY TRUEI", "DODGY FALSEI", + "DODGY IF", "DODGY TRUE", "DODGY FALSE", + "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE", + "DODGY ELSE", "DODGY ENDIF", + "PLAIN", "EOF" +}; + +/* state of #if processing */ +typedef enum { + IS_OUTSIDE, + IS_FALSE_PREFIX, /* false #if followed by false #elifs */ + IS_TRUE_PREFIX, /* first non-false #(el)if is true */ + IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */ + IS_FALSE_MIDDLE, /* a false #elif after a pass state */ + IS_TRUE_MIDDLE, /* a true #elif after a pass state */ + IS_PASS_ELSE, /* an else after a pass state */ + IS_FALSE_ELSE, /* an else after a true state */ + IS_TRUE_ELSE, /* an else after only false states */ + IS_FALSE_TRAILER, /* #elifs after a true are false */ + IS_COUNT +} Ifstate; + +static char const * const ifstate_name[] = { + "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX", + "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE", + "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE", + "FALSE_TRAILER" +}; + +/* state of comment parser */ +typedef enum { + NO_COMMENT = false, /* outside a comment */ + C_COMMENT, /* in a comment like this one */ + CXX_COMMENT, /* between // and end of line */ + STARTING_COMMENT, /* just after slash-backslash-newline */ + FINISHING_COMMENT /* star-backslash-newline in a C comment */ +} Comment_state; + +static char const * const comment_name[] = { + "NO", "C", "CXX", "STARTING", "FINISHING" +}; + +/* state of preprocessor line parser */ +typedef enum { + LS_START, /* only space and comments on this line */ + LS_HASH, /* only space, comments, and a hash */ + LS_DIRTY /* this line can't be a preprocessor line */ +} Line_state; + +static char const * const linestate_name[] = { + "START", "HASH", "DIRTY" +}; + +/* + * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1 + */ +#define MAXDEPTH 64 /* maximum #if nesting */ +#define MAXLINE 4096 /* maximum length of line */ +#define MAXSYMS 4096 /* maximum number of symbols */ + +/* + * Sometimes when editing a keyword the replacement text is longer, so + * we leave some space at the end of the tline buffer to accommodate this. + */ +#define EDITSLOP 10 + +/* + * Globals. + */ + +static bool complement; /* -c: do the complement */ +static bool debugging; /* -d: debugging reports */ +static bool iocccok; /* -e: fewer IOCCC errors */ +static bool killconsts; /* -k: eval constant #ifs */ +static bool lnblank; /* -l: blank deleted lines */ +static bool symlist; /* -s: output symbol list */ +static bool text; /* -t: this is a text file */ + +static const char *symname[MAXSYMS]; /* symbol name */ +static const char *value[MAXSYMS]; /* -Dsym=value */ +static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */ +static int nsyms; /* number of symbols */ + +static FILE *input; /* input file pointer */ +static FILE *output; /* output file pointer */ +static const char *filename; /* input file name */ +static char *ofilename; /* output file name */ +static char tmpname[MAXPATHLEN]; /* used when overwriting */ +static int linenum; /* current line number */ +static int overwriting; /* output overwrites input */ + +static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */ +static char *keyword; /* used for editing #elif's */ + +static Comment_state incomment; /* comment parser state */ +static Line_state linestate; /* #if line parser state */ +static Ifstate ifstate[MAXDEPTH]; /* #if processor state */ +static bool ignoring[MAXDEPTH]; /* ignore comments state */ +static int stifline[MAXDEPTH]; /* start of current #if */ +static int depth; /* current #if nesting */ +static bool keepthis; /* don't delete constant #if */ + +static int exitstat; /* program exit status */ + +static void addsym(bool, bool, char *); +static void debug(const char *, ...) __printflike(1, 2); +__dead static void done(void); +__dead static void error(const char *); +static int findsym(const char *); +static void flushline(bool); +static Linetype get_line(void); +static Linetype ifeval(const char **); +static void ignoreoff(void); +static void ignoreon(void); +static void keywordedit(const char *); +static void nest(void); +__dead static void process(void); +static const char *skipcomment(const char *); +static const char *skipsym(const char *); +static void state(Ifstate); +static int strlcmp(const char *, const char *, size_t); +__dead static void usage(void); + +#define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_') + +/* + * The main program. + */ +int +main(int argc, char *argv[]) +{ + int opt; + struct stat isb, osb; + + while ((opt = getopt(argc, argv, "i:D:U:I:o:cdeklst")) != -1) + switch (opt) { + case 'i': /* treat stuff controlled by these symbols as text */ + /* + * For strict backwards-compatibility the U or D + * should be immediately after the -i but it doesn't + * matter much if we relax that requirement. + */ + opt = *optarg++; + if (opt == 'D') + addsym(true, true, optarg); + else if (opt == 'U') + addsym(true, false, optarg); + else + usage(); + break; + case 'D': /* define a symbol */ + addsym(false, true, optarg); + break; + case 'U': /* undef a symbol */ + addsym(false, false, optarg); + break; + case 'I': + /* no-op for compatibility with cpp */ + break; + case 'c': /* treat -D as -U and vice versa */ + complement = true; + break; + case 'd': + debugging = true; + break; + case 'e': /* fewer errors from dodgy lines */ + iocccok = true; + break; + case 'k': /* process constant #ifs */ + killconsts = true; + break; + case 'l': /* blank deleted lines instead of omitting them */ + lnblank = true; + break; + case 'o': /* output to a file */ + ofilename = optarg; + break; + case 's': /* only output list of symbols that control #ifs */ + symlist = true; + break; + case 't': /* don't parse C comments */ + text = true; + break; + default: + usage(); + } + argc -= optind; + argv += optind; + if (nsyms == 0 && !symlist) { + warnx("must -D or -U at least one symbol"); + usage(); + } + if (argc > 1) { + errx(2, "can only do one file"); + } else if (argc == 1 && strcmp(*argv, "-") != 0) { + filename = *argv; + input = fopen(filename, "r"); + if (input == NULL) + err(2, "can't open %s", filename); + } else { + filename = "[stdin]"; + input = stdin; + } + if (ofilename == NULL) { + output = stdout; + } else { + if (stat(ofilename, &osb) == 0) { + if (fstat(fileno(input), &isb) != 0) + err(2, "can't fstat %s", filename); + + overwriting = (osb.st_dev == isb.st_dev && + osb.st_ino == isb.st_ino); + } + if (overwriting) { + int ofd; + + snprintf(tmpname, sizeof(tmpname), "%s/unifdef.XXXXXX", + dirname(ofilename)); + if ((ofd = mkstemp(tmpname)) != -1) + output = fdopen(ofd, "w+"); + if (output == NULL) + err(2, "can't create temporary file"); + fchmod(ofd, isb.st_mode & ACCESSPERMS); + } else { + output = fopen(ofilename, "w"); + if (output == NULL) + err(2, "can't open %s", ofilename); + } + } + process(); + abort(); /* bug */ +} + +static void +usage(void) +{ + fprintf(stderr, "usage: unifdef [-cdeklst] [-o output]" + " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n"); + exit(2); +} + +/* + * A state transition function alters the global #if processing state + * in a particular way. The table below is indexed by the current + * processing state and the type of the current line. + * + * Nesting is handled by keeping a stack of states; some transition + * functions increase or decrease the depth. They also maintain the + * ignore state on a stack. In some complicated cases they have to + * alter the preprocessor directive, as follows. + * + * When we have processed a group that starts off with a known-false + * #if/#elif sequence (which has therefore been deleted) followed by a + * #elif that we don't understand and therefore must keep, we edit the + * latter into a #if to keep the nesting correct. + * + * When we find a true #elif in a group, the following block will + * always be kept and the rest of the sequence after the next #elif or + * #else will be discarded. We edit the #elif into a #else and the + * following directive to #endif since this has the desired behaviour. + * + * "Dodgy" directives are split across multiple lines, the most common + * example being a multi-line comment hanging off the right of the + * directive. We can handle them correctly only if there is no change + * from printing to dropping (or vice versa) caused by that directive. + * If the directive is the first of a group we have a choice between + * failing with an error, or passing it through unchanged instead of + * evaluating it. The latter is not the default to avoid questions from + * users about unifdef unexpectedly leaving behind preprocessor directives. + */ +typedef void state_fn(void); + +/* report an error */ +__dead static void Eelif (void) { error("Inappropriate #elif"); } +__dead static void Eelse (void) { error("Inappropriate #else"); } +__dead static void Eendif(void) { error("Inappropriate #endif"); } +__dead static void Eeof (void) { error("Premature EOF"); } +__dead static void Eioccc(void) { error("Obfuscated preprocessor control line"); } +/* plain line handling */ +static void print (void) { flushline(true); } +static void drop (void) { flushline(false); } +/* output lacks group's start line */ +static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); } +static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); } +static void Selse (void) { drop(); state(IS_TRUE_ELSE); } +/* print/pass this block */ +static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); } +static void Pelse (void) { print(); state(IS_PASS_ELSE); } +static void Pendif(void) { print(); --depth; } +/* discard this block */ +static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); } +static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); } +static void Delse (void) { drop(); state(IS_FALSE_ELSE); } +static void Dendif(void) { drop(); --depth; } +/* first line of group */ +static void Fdrop (void) { nest(); Dfalse(); } +static void Fpass (void) { nest(); Pelif(); } +static void Ftrue (void) { nest(); Strue(); } +static void Ffalse(void) { nest(); Sfalse(); } +/* variable pedantry for obfuscated lines */ +static void Oiffy (void) { if (iocccok) Fpass(); else Eioccc(); ignoreon(); } +static void Oif (void) { if (iocccok) Fpass(); else Eioccc(); } +static void Oelif (void) { if (iocccok) Pelif(); else Eioccc(); } +/* ignore comments in this block */ +static void Idrop (void) { Fdrop(); ignoreon(); } +static void Itrue (void) { Ftrue(); ignoreon(); } +static void Ifalse(void) { Ffalse(); ignoreon(); } +/* edit this line */ +static void Mpass (void) { strncpy(keyword, "if ", 4); Pelif(); } +static void Mtrue (void) { keywordedit("else\n"); state(IS_TRUE_MIDDLE); } +static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER); } +static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE); } + +static state_fn * const trans_table[IS_COUNT][LT_COUNT] = { +/* IS_OUTSIDE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif, + print, done }, +/* IS_FALSE_PREFIX */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc, + drop, Eeof }, +/* IS_TRUE_PREFIX */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, + print, Eeof }, +/* IS_PASS_MIDDLE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif, + print, Eeof }, +/* IS_FALSE_MIDDLE */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc, + drop, Eeof }, +/* IS_TRUE_MIDDLE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif, + print, Eeof }, +/* IS_PASS_ELSE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif, + print, Eeof }, +/* IS_FALSE_ELSE */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc, + drop, Eeof }, +/* IS_TRUE_ELSE */ +{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif, + Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc, + print, Eeof }, +/* IS_FALSE_TRAILER */ +{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif, + Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc, + drop, Eeof } +/*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF + TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY) + PLAIN EOF */ +}; + +/* + * State machine utility functions + */ +static void +done(void) +{ + if (incomment) + error("EOF in comment"); + if (fclose(output)) { + if (overwriting) { + unlink(tmpname); + errx(2, "%s unchanged", ofilename); + } + } + if (overwriting && rename(tmpname, ofilename)) { + unlink(tmpname); + errx(2, "%s unchanged", ofilename); + } + exit(exitstat); +} +static void +ignoreoff(void) +{ + ignoring[depth] = ignoring[depth-1]; +} +static void +ignoreon(void) +{ + ignoring[depth] = true; +} +static void +keywordedit(const char *replacement) +{ + strlcpy(keyword, replacement, tline + sizeof(tline) - keyword); + print(); +} +static void +nest(void) +{ + depth += 1; + if (depth >= MAXDEPTH) + error("Too many levels of nesting"); + stifline[depth] = linenum; +} +static void +state(Ifstate is) +{ + ifstate[depth] = is; +} + +/* + * Write a line to the output or not, according to command line options. + */ +static void +flushline(bool keep) +{ + if (symlist) + return; + if (keep ^ complement) + fputs(tline, output); + else { + if (lnblank) + putc('\n', output); + exitstat = 1; + } +} + +/* + * The driver for the state machine. + */ +static void +process(void) +{ + Linetype lineval; + + for (;;) { + linenum++; + lineval = get_line(); + trans_table[ifstate[depth]][lineval](); + debug("process %s -> %s depth %d", + linetype_name[lineval], + ifstate_name[ifstate[depth]], depth); + } +} + +/* + * Parse a line and determine its type. We keep the preprocessor line + * parser state between calls in the global variable linestate, with + * help from skipcomment(). + */ +static Linetype +get_line(void) +{ + const char *cp; + int cursym; + int kwlen; + Linetype retval; + Comment_state wascomment; + + if (fgets(tline, MAXLINE, input) == NULL) + return (LT_EOF); + retval = LT_PLAIN; + wascomment = incomment; + cp = skipcomment(tline); + if (linestate == LS_START) { + if (*cp == '#') { + linestate = LS_HASH; + cp = skipcomment(cp + 1); + } else if (*cp != '\0') + linestate = LS_DIRTY; + } + if (!incomment && linestate == LS_HASH) { + keyword = tline + (cp - tline); + cp = skipsym(cp); + kwlen = cp - keyword; + /* no way can we deal with a continuation inside a keyword */ + if (strncmp(cp, "\\\n", 2) == 0) + Eioccc(); + if (strlcmp("ifdef", keyword, kwlen) == 0 || + strlcmp("ifndef", keyword, kwlen) == 0) { + cp = skipcomment(cp); + if ((cursym = findsym(cp)) < 0) + retval = LT_IF; + else { + retval = (keyword[2] == 'n') + ? LT_FALSE : LT_TRUE; + if (value[cursym] == NULL) + retval = (retval == LT_TRUE) + ? LT_FALSE : LT_TRUE; + if (ignore[cursym]) + retval = (retval == LT_TRUE) + ? LT_TRUEI : LT_FALSEI; + } + cp = skipsym(cp); + } else if (strlcmp("if", keyword, kwlen) == 0) + retval = ifeval(&cp); + else if (strlcmp("elif", keyword, kwlen) == 0) + retval = ifeval(&cp) - LT_IF + LT_ELIF; + else if (strlcmp("else", keyword, kwlen) == 0) + retval = LT_ELSE; + else if (strlcmp("endif", keyword, kwlen) == 0) + retval = LT_ENDIF; + else { + linestate = LS_DIRTY; + retval = LT_PLAIN; + } + cp = skipcomment(cp); + if (*cp != '\0') { + linestate = LS_DIRTY; + if (retval == LT_TRUE || retval == LT_FALSE || + retval == LT_TRUEI || retval == LT_FALSEI) + retval = LT_IF; + if (retval == LT_ELTRUE || retval == LT_ELFALSE) + retval = LT_ELIF; + } + if (retval != LT_PLAIN && (wascomment || incomment)) { + retval += LT_DODGY; + if (incomment) + linestate = LS_DIRTY; + } + } + if (linestate == LS_DIRTY) { + while (*cp != '\0') + cp = skipcomment(cp + 1); + } + debug("parser %s comment %s line", + comment_name[incomment], linestate_name[linestate]); + return (retval); +} + +/* + * These are the binary operators that are supported by the expression + * evaluator. Note that if support for division is added then we also + * need short-circuiting booleans because of divide-by-zero. + */ +static int op_lt(int a, int b) { return (a < b); } +static int op_gt(int a, int b) { return (a > b); } +static int op_le(int a, int b) { return (a <= b); } +static int op_ge(int a, int b) { return (a >= b); } +static int op_eq(int a, int b) { return (a == b); } +static int op_ne(int a, int b) { return (a != b); } +static int op_or(int a, int b) { return (a || b); } +static int op_and(int a, int b) { return (a && b); } + +/* + * An evaluation function takes three arguments, as follows: (1) a pointer to + * an element of the precedence table which lists the operators at the current + * level of precedence; (2) a pointer to an integer which will receive the + * value of the expression; and (3) a pointer to a char* that points to the + * expression to be evaluated and that is updated to the end of the expression + * when evaluation is complete. The function returns LT_FALSE if the value of + * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the + * expression could not be evaluated. + */ +struct ops; + +typedef Linetype eval_fn(const struct ops *, int *, const char **); + +static eval_fn eval_table, eval_unary; + +/* + * The precedence table. Expressions involving binary operators are evaluated + * in a table-driven way by eval_table. When it evaluates a subexpression it + * calls the inner function with its first argument pointing to the next + * element of the table. Innermost expressions have special non-table-driven + * handling. + */ +static const struct ops { + eval_fn *inner; + struct op { + const char *str; + int (*fn)(int, int); + } op[5]; +} eval_ops[] = { + { eval_table, { { "||", op_or } } }, + { eval_table, { { "&&", op_and } } }, + { eval_table, { { "==", op_eq }, + { "!=", op_ne } } }, + { eval_unary, { { "<=", op_le }, + { ">=", op_ge }, + { "<", op_lt }, + { ">", op_gt } } } +}; + +/* + * Function for evaluating the innermost parts of expressions, + * viz. !expr (expr) defined(symbol) symbol number + * We reset the keepthis flag when we find a non-constant subexpression. + */ +static Linetype +eval_unary(const struct ops *ops, int *valp, const char **cpp) +{ + const char *cp; + char *ep; + int sym; + + cp = skipcomment(*cpp); + if (*cp == '!') { + debug("eval%td !", ops - eval_ops); + cp++; + if (eval_unary(ops, valp, &cp) == LT_IF) + return (LT_IF); + *valp = !*valp; + } else if (*cp == '(') { + cp++; + debug("eval%td (", ops - eval_ops); + if (eval_table(eval_ops, valp, &cp) == LT_IF) + return (LT_IF); + cp = skipcomment(cp); + if (*cp++ != ')') + return (LT_IF); + } else if (isdigit((unsigned char)*cp)) { + debug("eval%td number", ops - eval_ops); + *valp = strtol(cp, &ep, 0); + cp = skipsym(cp); + } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) { + cp = skipcomment(cp+7); + debug("eval%td defined", ops - eval_ops); + if (*cp++ != '(') + return (LT_IF); + cp = skipcomment(cp); + sym = findsym(cp); + if (sym < 0 || symlist) + return (LT_IF); + *valp = (value[sym] != NULL); + cp = skipsym(cp); + cp = skipcomment(cp); + if (*cp++ != ')') + return (LT_IF); + keepthis = false; + } else if (!endsym(*cp)) { + debug("eval%td symbol", ops - eval_ops); + sym = findsym(cp); + if (sym < 0 || symlist) + return (LT_IF); + if (value[sym] == NULL) + *valp = 0; + else { + *valp = strtol(value[sym], &ep, 0); + if (*ep != '\0' || ep == value[sym]) + return (LT_IF); + } + cp = skipsym(cp); + keepthis = false; + } else { + debug("eval%td bad expr", ops - eval_ops); + return (LT_IF); + } + + *cpp = cp; + debug("eval%td = %d", ops - eval_ops, *valp); + return (*valp ? LT_TRUE : LT_FALSE); +} + +/* + * Table-driven evaluation of binary operators. + */ +static Linetype +eval_table(const struct ops *ops, int *valp, const char **cpp) +{ + const struct op *op; + const char *cp; + int val; + + debug("eval%td", ops - eval_ops); + cp = *cpp; + if (ops->inner(ops+1, valp, &cp) == LT_IF) + return (LT_IF); + for (;;) { + cp = skipcomment(cp); + for (op = ops->op; op->str != NULL; op++) + if (strncmp(cp, op->str, strlen(op->str)) == 0) + break; + if (op->str == NULL) + break; + cp += strlen(op->str); + debug("eval%td %s", ops - eval_ops, op->str); + if (ops->inner(ops+1, &val, &cp) == LT_IF) + return (LT_IF); + *valp = op->fn(*valp, val); + } + + *cpp = cp; + debug("eval%td = %d", ops - eval_ops, *valp); + return (*valp ? LT_TRUE : LT_FALSE); +} + +/* + * Evaluate the expression on a #if or #elif line. If we can work out + * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we + * return just a generic LT_IF. + */ +static Linetype +ifeval(const char **cpp) +{ + int ret; + int val; + + debug("eval %s", *cpp); + keepthis = killconsts ? false : true; + ret = eval_table(eval_ops, &val, cpp); + debug("eval = %d", val); + return (keepthis ? LT_IF : ret); +} + +/* + * Skip over comments and stop at the next character position that is + * not whitespace. Between calls we keep the comment state in the + * global variable incomment, and we also adjust the global variable + * linestate when we see a newline. + * XXX: doesn't cope with the buffer splitting inside a state transition. + */ +static const char * +skipcomment(const char *cp) +{ + if (text || ignoring[depth]) { + for (; isspace((unsigned char)*cp); cp++) + if (*cp == '\n') + linestate = LS_START; + return (cp); + } + while (*cp != '\0') + /* don't reset to LS_START after a line continuation */ + if (strncmp(cp, "\\\n", 2) == 0) + cp += 2; + else switch (incomment) { + case NO_COMMENT: + if (strncmp(cp, "/\\\n", 3) == 0) { + incomment = STARTING_COMMENT; + cp += 3; + } else if (strncmp(cp, "/*", 2) == 0) { + incomment = C_COMMENT; + cp += 2; + } else if (strncmp(cp, "//", 2) == 0) { + incomment = CXX_COMMENT; + cp += 2; + } else if (strncmp(cp, "\n", 1) == 0) { + linestate = LS_START; + cp += 1; + } else if (strchr(" \t", *cp) != NULL) { + cp += 1; + } else + return (cp); + continue; + case CXX_COMMENT: + if (strncmp(cp, "\n", 1) == 0) { + incomment = NO_COMMENT; + linestate = LS_START; + } + cp += 1; + continue; + case C_COMMENT: + if (strncmp(cp, "*\\\n", 3) == 0) { + incomment = FINISHING_COMMENT; + cp += 3; + } else if (strncmp(cp, "*/", 2) == 0) { + incomment = NO_COMMENT; + cp += 2; + } else + cp += 1; + continue; + case STARTING_COMMENT: + if (*cp == '*') { + incomment = C_COMMENT; + cp += 1; + } else if (*cp == '/') { + incomment = CXX_COMMENT; + cp += 1; + } else { + incomment = NO_COMMENT; + linestate = LS_DIRTY; + } + continue; + case FINISHING_COMMENT: + if (*cp == '/') { + incomment = NO_COMMENT; + cp += 1; + } else + incomment = C_COMMENT; + continue; + default: + abort(); /* bug */ + } + return (cp); +} + +/* + * Skip over an identifier. + */ +static const char * +skipsym(const char *cp) +{ + while (!endsym(*cp)) + ++cp; + return (cp); +} + +/* + * Look for the symbol in the symbol table. If it is found, we return + * the symbol table index, else we return -1. + */ +static int +findsym(const char *str) +{ + const char *cp; + int symind; + + cp = skipsym(str); + if (cp == str) + return (-1); + if (symlist) + printf("%.*s\n", (int)(cp-str), str); + for (symind = 0; symind < nsyms; ++symind) { + if (strlcmp(symname[symind], str, cp-str) == 0) { + debug("findsym %s %s", symname[symind], + value[symind] ? value[symind] : ""); + return (symind); + } + } + return (-1); +} + +/* + * Add a symbol to the symbol table. + */ +static void +addsym(bool ignorethis, bool definethis, char *sym) +{ + int symind; + char *val; + + symind = findsym(sym); + if (symind < 0) { + if (nsyms >= MAXSYMS) + errx(2, "too many symbols"); + symind = nsyms++; + } + symname[symind] = sym; + ignore[symind] = ignorethis; + val = sym + (skipsym(sym) - sym); + if (definethis) { + if (*val == '=') { + value[symind] = val+1; + *val = '\0'; + } else if (*val == '\0') + value[symind] = ""; + else + usage(); + } else { + if (*val != '\0') + usage(); + value[symind] = NULL; + } +} + +/* + * Compare s with n characters of t. + * The same as strncmp() except that it checks that s[n] == '\0'. + */ +static int +strlcmp(const char *s, const char *t, size_t n) +{ + while (n-- && *t != '\0') + if (*s != *t) + return ((unsigned char)*s - (unsigned char)*t); + else + ++s, ++t; + return ((unsigned char)*s); +} + +/* + * Diagnostics. + */ +static void +debug(const char *msg, ...) +{ + va_list ap; + + if (debugging) { + va_start(ap, msg); + vwarnx(msg, ap); + va_end(ap); + } +} + +static void +error(const char *msg) +{ + if (depth == 0) + warnx("%s: %d: %s", filename, linenum, msg); + else + warnx("%s: %d: %s (#if line %d depth %d)", + filename, linenum, msg, stifline[depth], depth); + fclose(output); + if (overwriting) { + unlink(tmpname); + errx(2, "%s unchanged", ofilename); + } + errx(2, "output may be truncated"); +} diff --git a/usr.bin/unifdef/unifdefall.sh b/usr.bin/unifdef/unifdefall.sh new file mode 100644 index 0000000..7c1d643 --- /dev/null +++ b/usr.bin/unifdef/unifdefall.sh @@ -0,0 +1,29 @@ +#!/bin/sh +# +# remove all the #if's from a source file +# +# $dotat: things/unifdefall.sh,v 1.10 2002/09/24 19:52:11 fanf2 Exp $ +# $FreeBSD: src/usr.bin/unifdef/unifdefall.sh,v 1.2 2002/09/24 19:50:03 fanf Exp $ + +set -e + +basename=`basename $0` +tmp=`mktemp -d -t $basename` || exit 2 + +unifdef -s "$@" | sort | uniq > $tmp/ctrl +cpp -dM "$@" | sort | + sed -Ee 's/^#define[ ]+(.*[^ ])[ ]*$/\1/' > $tmp/hashdefs +sed -Ee 's/^([A-Za-z0-9_]+).*$/\1/' $tmp/hashdefs > $tmp/alldef +comm -23 $tmp/ctrl $tmp/alldef > $tmp/undef +comm -12 $tmp/ctrl $tmp/alldef > $tmp/def + +echo unifdef -k \\ > $tmp/cmd +sed -Ee 's/^(.*)$/-U\1 \\/' $tmp/undef >> $tmp/cmd +while read sym +do sed -Ee '/^('"$sym"')([(][^)]*[)])?([ ]+(.*))?$/!d;s//-D\1=\4/' $tmp/hashdefs +done < $tmp/def | + sed -Ee 's/\\/\\\\/g;s/"/\\"/g;s/^/"/;s/$/" \\/' >> $tmp/cmd +echo '"$@"' >> $tmp/cmd +sh $tmp/cmd "$@" + +rm -r $tmp diff --git a/usr.bin/uniq/uniq.1 b/usr.bin/uniq/uniq.1 new file mode 100644 index 0000000..bf1a78c --- /dev/null +++ b/usr.bin/uniq/uniq.1 @@ -0,0 +1,130 @@ +.\" $NetBSD: uniq.1,v 1.13 2012/08/26 14:13:50 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)uniq.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd August 26, 2012 +.Dt UNIQ 1 +.Os +.Sh NAME +.Nm uniq +.Nd report or filter out repeated lines in a file +.Sh SYNOPSIS +.Nm +.Op Fl cdu +.Op Fl f Ar fields +.Op Fl s Ar chars +.Oo +.Ar input_file +.Op Ar output_file +.Oc +.Sh DESCRIPTION +The +.Nm +utility reads the standard input comparing adjacent lines, and writes +a copy of each unique input line to the standard output. +The second and succeeding copies of identical adjacent input lines are +not written. +Repeated lines in the input will not be detected if they are not adjacent, +so it may be necessary to sort the files first. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl c +Precede each output line with the count of the number of times the line +occurred in the input, followed by a single space. +.It Fl d +Don't output lines that are not repeated in the input. +.It Fl f Ar fields +Ignore the first +.Ar fields +in each input line when doing comparisons. +A field is a string of non-blank characters separated from adjacent fields +by blanks. +Field numbers are one based, i.e., the first field is field one. +.It Fl s Ar chars +Ignore the first +.Ar chars +characters in each input line when doing comparisons. +If specified in conjunction with the +.Fl f +option, the first +.Ar chars +characters after the first +.Ar fields +fields will be ignored. +Character numbers are one based, i.e., the first character is character one. +.It Fl u +Don't output lines that are repeated in the input. +.\".It Fl Ns Ar n +.\"(Deprecated; replaced by +.\".Fl f ) . +.\"Ignore the first n +.\"fields on each input line when doing comparisons, +.\"where n is a number. +.\"A field is a string of non-blank +.\"characters separated from adjacent fields +.\"by blanks. +.\".It Cm \&\(pl Ns Ar n +.\"(Deprecated; replaced by +.\".Fl s ) . +.\"Ignore the first +.\".Ar m +.\"characters when doing comparisons, where +.\".Ar m +.\"is a +.\"number. +.El +.Pp +Additional arguments +.Ar input_file +and +.Ar output_file +may be specified on the command line, +where the former is then used as the name of an input file, +and the latter as the name of an output file. +.Sh EXIT STATUS +.Ex -std +.Sh COMPATIBILITY +The historic +.Cm \&\(pl Ns Ar number +and +.Fl Ns Ar number +options have been deprecated but are still supported in this implementation. +.Sh SEE ALSO +.Xr sort 1 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compatible. diff --git a/usr.bin/uniq/uniq.c b/usr.bin/uniq/uniq.c new file mode 100644 index 0000000..ded7037 --- /dev/null +++ b/usr.bin/uniq/uniq.c @@ -0,0 +1,266 @@ +/* $NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Case Larsen. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)uniq.c 8.3 (Berkeley) 5/4/95"; +#endif +__RCSID("$NetBSD: uniq.c,v 1.20 2016/10/16 06:17:51 abhinav Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include + +static int cflag, dflag, uflag; +static int numchars, numfields, repeats; + +static FILE *file(const char *, const char *); +static void show(FILE *, const char *); +static const char *skip(const char *, size_t *); +static void obsolete(char *[]); +static void usage(void) __dead; + +int +main (int argc, char *argv[]) +{ + const char *prevp, *thisp; + FILE *ifp, *ofp; + int ch; + char *prevline, *thisline, *p; + size_t prevlinesize, thislinesize, psize; + size_t prevlen, thislen; + + setprogname(argv[0]); + ifp = ofp = NULL; + obsolete(argv); + while ((ch = getopt(argc, argv, "-cdf:s:u")) != -1) + switch (ch) { + case '-': + --optind; + goto done; + case 'c': + cflag = 1; + break; + case 'd': + dflag = 1; + break; + case 'f': + numfields = strtol(optarg, &p, 10); + if (numfields < 0 || *p) + errx(1, "illegal field skip value: %s", optarg); + break; + case 's': + numchars = strtol(optarg, &p, 10); + if (numchars < 0 || *p) + errx(1, "illegal character skip value: %s", + optarg); + break; + case 'u': + uflag = 1; + break; + case '?': + default: + usage(); + } + +done: argc -= optind; + argv +=optind; + + switch(argc) { + case 0: + ifp = stdin; + ofp = stdout; + break; + case 1: + ifp = file(argv[0], "r"); + ofp = stdout; + break; + case 2: + ifp = file(argv[0], "r"); + ofp = file(argv[1], "w"); + break; + default: + usage(); + } + + if ((p = fgetln(ifp, &psize)) == NULL) + return 0; + prevlinesize = prevlen = psize; + if ((prevline = malloc(prevlinesize + 1)) == NULL) + err(1, "malloc"); + (void)memcpy(prevline, p, prevlinesize); + prevline[prevlinesize] = '\0'; + + if (numfields || numchars) + prevp = skip(prevline, &prevlen); + else + prevp = prevline; + + thislinesize = psize; + if ((thisline = malloc(thislinesize + 1)) == NULL) + err(1, "malloc"); + + while ((p = fgetln(ifp, &psize)) != NULL) { + if (psize > thislinesize) { + if ((thisline = realloc(thisline, psize + 1)) == NULL) + err(1, "realloc"); + thislinesize = psize; + } + thislen = psize; + (void)memcpy(thisline, p, psize); + thisline[psize] = '\0'; + + /* If requested get the chosen fields + character offsets. */ + if (numfields || numchars) { + thisp = skip(thisline, &thislen); + } else { + thisp = thisline; + } + + /* If different, print; set previous to new value. */ + if (thislen != prevlen || strcmp(thisp, prevp)) { + char *t; + size_t ts; + + show(ofp, prevline); + t = prevline; + prevline = thisline; + thisline = t; + ts = prevlinesize; + prevlinesize = thislinesize; + thislinesize = ts; + prevp = thisp; + prevlen = thislen; + repeats = 0; + } else + ++repeats; + } + show(ofp, prevline); + free(prevline); + free(thisline); + return 0; +} + +/* + * show -- + * Output a line depending on the flags and number of repetitions + * of the line. + */ +static void +show(FILE *ofp, const char *str) +{ + + if ((dflag && repeats == 0) || (uflag && repeats > 0)) + return; + if (cflag) { + (void)fprintf(ofp, "%4d %s", repeats + 1, str); + } else { + (void)fprintf(ofp, "%s", str); + } +} + +static const char * +skip(const char *str, size_t *linesize) +{ + int infield, nchars, nfields; + size_t ls = *linesize; + + for (nfields = numfields, infield = 0; nfields && *str; ++str, --ls) + if (isspace((unsigned char)*str)) { + if (infield) { + infield = 0; + --nfields; + } + } else if (!infield) + infield = 1; + for (nchars = numchars; nchars-- && *str; ++str, --ls) + continue; + *linesize = ls; + return str; +} + +static FILE * +file(const char *name, const char *mode) +{ + FILE *fp; + + if ((fp = fopen(name, mode)) == NULL) + err(1, "%s", name); + return(fp); +} + +static void +obsolete(char *argv[]) +{ + char *ap, *p, *start; + + while ((ap = *++argv) != NULL) { + /* Return if "--" or not an option of any form. */ + if (ap[0] != '-') { + if (ap[0] != '+') + return; + } else if (ap[1] == '-') + return; + if (!isdigit((unsigned char)ap[1])) + continue; + /* + * Digit signifies an old-style option. Malloc space for dash, + * new option and argument. + */ + (void)asprintf(&p, "-%c%s", ap[0] == '+' ? 's' : 'f', ap + 1); + if (!p) + err(1, "malloc"); + start = p; + *argv = start; + } +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-cdu] [-f fields] [-s chars] " + "[input_file [output_file]]\n", getprogname()); + exit(1); +} diff --git a/usr.bin/uudecode/uudecode.c b/usr.bin/uudecode/uudecode.c new file mode 100644 index 0000000..977ace2 --- /dev/null +++ b/usr.bin/uudecode/uudecode.c @@ -0,0 +1,323 @@ +/* $NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $ */ + +/*- + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if HAVE_NBTOOL_CONFIG_H +#include "nbtool_config.h" +#endif + +#include +#if !defined(lint) +__COPYRIGHT("@(#) Copyright (c) 1983, 1993\ + The Regents of the University of California. All rights reserved."); +#if 0 +static char sccsid[] = "@(#)uudecode.c 8.2 (Berkeley) 4/2/94"; +#endif +__RCSID("$NetBSD: uudecode.c,v 1.28 2013/01/28 19:50:30 apb Exp $"); +#endif /* not lint */ + +/* + * uudecode [file ...] + * + * create the specified file, decoding as you go. + * used with uuencode. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef NO_BASE64 +#include +#include +#endif + +static int decode(char *); +__dead static void usage(void); +static int checkend(const char *, const char *, const char *); +static int base64_decode(void); + +static int base64; +static const char *inputname; + +int +main(int argc, char *argv[]) +{ + int ch, rval; + char *outputname = NULL; + + setlocale(LC_ALL, ""); + setprogname(argv[0]); + + while ((ch = getopt(argc, argv, "mo:p")) != -1) + switch (ch) { + case 'm': + base64 = 1; + break; + case 'o': + outputname = optarg; + break; + case 'p': + outputname = __UNCONST("/dev/stdout"); + break; + default: + usage(); + } + argc -= optind; + argv += optind; + + if (*argv) { + rval = 0; + do { + if (!freopen(inputname = *argv, "r", stdin)) { + warn("%s", *argv); + rval = 1; + continue; + } + rval |= decode(outputname); + } while (*++argv); + } else { + inputname = "stdin"; + rval = decode(outputname); + } + exit(rval); +} + +/* + * Decode one file from stdin. If outputname is not NULL + * then it overrides the file name embedded in the input data. + */ +static int +decode(char *outputname) +{ + struct passwd *pw; + int n; + char ch, *p; + int n1; + long mode; + char *fn; + char buf[MAXPATHLEN]; + + /* search for header line */ + for (;;) { + if (!fgets(buf, sizeof(buf), stdin)) { + warnx("%s: no \"%s\" line", inputname, base64 ? + "begin-base64" : "begin"); + return(1); + } + p = buf; + if (strncmp(p, "begin-base64", 12) == 0) { + base64 = 1; + p += 13; + break; + } else if (strncmp(p, "begin", 5) == 0) { + p += 6; + break; + } else + continue; + } + + /* must be followed by an octal mode and a space */ + mode = strtol(p, &fn, 8); + if (fn == (p) || !isspace((unsigned char)*fn) || mode==LONG_MIN || mode==LONG_MAX) + { + warnx("%s: invalid mode on \"%s\" line", inputname, + base64 ? "begin-base64" : "begin"); + return(1); + } + /* skip whitespace for file name */ + while (*fn && isspace((unsigned char)*fn)) fn++; + if (*fn == 0) { + warnx("%s: no filename on \"%s\" line", inputname, + base64 ? "begin-base64" : "begin"); + return(1); + } + /* zap newline */ + for (p = fn; *p && *p != '\n'; p++) + ; + if (*p) *p = 0; + + /* outputname overrides fn */ + if (outputname) + fn = outputname; + + /* handle ~user/file format */ + if (*fn == '~') { + if (!(p = strchr(fn, '/'))) { + warnx("%s: illegal ~user.", inputname); + return(1); + } + *p++ = '\0'; + if (!(pw = getpwnam(fn + 1))) { + warnx("%s: no user %s.", inputname, buf); + return(1); + } + n = strlen(pw->pw_dir); + n1 = strlen(p); + if (n + n1 + 2 > MAXPATHLEN) { + warnx("%s: path too long.", inputname); + return(1); + } + /* make space at beginning of buf by moving end of pathname */ + memmove(buf + n + 1, p, n1 + 1); + memmove(buf, pw->pw_dir, n); + buf[n] = '/'; + fn = buf; + } + + if (strcmp(fn, "/dev/stdout") == 0 || strcmp(fn, "-") == 0) { + /* + * POSIX.1-2008 says that both "-" and "/dev/stdout" + * refer to standard output when they appear in the file + * header, but only "/dev/stdout" refers to standard + * output when it appears as the argument to the "-o" + * command line option. + * + * We handle both special names, regardless of whether + * they came from the "-o" option or from the header of + * the input stream. + */ + } else { + /* + * Create output file, and set its mode. POSIX.1-2008 + * requires the mode to be used exactly, ignoring the + * umask and anything else, but we mask it with 0666. + */ + if (freopen(fn, "w", stdout) == NULL || + fchmod(fileno(stdout), mode & 0666) != 0) { + warn("%s: %s", fn, inputname); + return(1); + } + } + + if (base64) + return base64_decode(); + else { + /* for each input line */ + for (;;) { + if (!fgets(p = buf, sizeof(buf), stdin)) { + warnx("%s: short file.", inputname); + return(1); + } +#define DEC(c) (((c) - ' ') & 077) /* single character decode */ + /* + * `n' is used to avoid writing out all the characters + * at the end of the file. + */ + if ((n = DEC(*p)) <= 0) + break; + for (++p; n > 0; p += 4, n -= 3) + if (n >= 3) { + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putchar(ch); + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putchar(ch); + ch = DEC(p[2]) << 6 | DEC(p[3]); + putchar(ch); + } + else { + if (n >= 1) { + ch = DEC(p[0]) << 2 | DEC(p[1]) >> 4; + putchar(ch); + } + if (n >= 2) { + ch = DEC(p[1]) << 4 | DEC(p[2]) >> 2; + putchar(ch); + } + if (n >= 3) { + ch = DEC(p[2]) << 6 | DEC(p[3]); + putchar(ch); + } + } + } + if (!fgets(buf, sizeof(buf), stdin) || strcmp(buf, "end\n")) { + warnx("%s: no \"end\" line.", inputname); + return(1); + } + return(0); + } +} + +static int +checkend(const char *ptr, const char *end, const char *msg) +{ + size_t n; + + n = strlen(end); + if (strncmp(ptr, end, n) != 0 || + strspn(ptr + n, "\t\r\n") != strlen(ptr + n)) { + warnx("%s", msg); + return (1); + } + return (0); +} + +static int +base64_decode(void) +{ + int n; + char inbuf[MAXPATHLEN]; + unsigned char outbuf[MAXPATHLEN * 4]; + + for (;;) { + if (!fgets(inbuf, sizeof(inbuf), stdin)) { + warnx("%s: short file.", inputname); + return (1); + } +#ifdef NO_BASE64 + warnx("%s: base64 decoding is not supported", inputname); + return (1); +#else + n = b64_pton(inbuf, outbuf, sizeof(outbuf)); +#endif + if (n < 0) + break; + fwrite(outbuf, 1, n, stdout); + } + return (checkend(inbuf, "====", + "error decoding base64 input stream")); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-m] [-p | -o outfile] [encoded-file ...]\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/uuencode/uuencode.1 b/usr.bin/uuencode/uuencode.1 new file mode 100644 index 0000000..fabd310 --- /dev/null +++ b/usr.bin/uuencode/uuencode.1 @@ -0,0 +1,175 @@ +.\" $NetBSD: uuencode.1,v 1.27 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1990, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)uuencode.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd September 6, 2014 +.Dt UUENCODE 1 +.Os +.Sh NAME +.Nm uuencode , +.Nm uudecode +.Nd encode/decode a binary file +.Sh SYNOPSIS +.Nm +.Op Fl m +.Op Ar inputfile +.Ar headername +.Nm uudecode +.Op Fl m +.Op Fl p | Fl o Ar outputfile +.Op Ar encoded-file ... +.Sh DESCRIPTION +.Nm +and +.Nm uudecode +are used to transmit binary files over transmission mediums +that do not support other than simple +.Tn ASCII +data. +.Pp +The following options are available: +.Bl -tag -width ".Fl m" +.It Fl m +Use base64 encoding. +For +.Nm , +the historical uuencode algorithm is the default. +For +.Nm uudecode , +by default the encoding is automatically detected. +.It Fl o Ar outputfile +.Po Nm uudecode No only . Pc +Send the decoded output data to +.Ar outputfile . +By default, +.Nm uudecode +uses the +.Ar headername +recorded in the header of the encoded data stream. +.It Fl p +.Po Nm uudecode No only . Pc +Write the decoded file to standard output instead of to a file. +.El +.Pp +.Nm +reads +.Ar inputfile +(or by default the standard input) and writes an encoded version +to (always) the standard output. +The encoding uses only printing +.Tn ASCII +characters suitable for text-only transport media. +The string +.Ar headername +is inserted into the output header as the +.Ar outputfile +to use at +.Nm uudecode +time. +The header also includes the mode (permissions) of the file. +.Pp +.Nm uudecode +transforms +.Em uuencoded +files (or by default, the standard input) into the original form. +The resulting file is named +.Ar headername +as recorded in the encoded file, +or as specified by the +.Fl o +option, +and will have the mode of the original file except that setuid +and execute bits are not retained. +If the +.Fl p +option is specified, or if the output file name is given as +.Pa /dev/stdout , +then the data will be written to the standard output +instead of to a named file. +.Nm uudecode +ignores any leading and trailing lines. +.Pp +The encoded form of the file is expanded by 35%. +Every 3 bytes become 4 plus control information. +.Sh EXIT STATUS +The +.Nm uudecode +and +.Nm +utilities exits 0 on success, and >0 if an error occurs. +.Sh EXAMPLES +The following example packages up a source tree, compresses it, +uuencodes it and mails it to a user on another system. +.Pp +.Bd -literal -offset indent -compact +tar czf \- src_tree \&| uuencode src_tree.tgz \&| mail user@example.com +.Ed +.Pp +On the other system, if the user saves the mail to the file +.Pa temp , +the following example creates the file +.Pa src_tree.tgz +and extracts it to make a copy of the original tree. +.Pp +.Bd -literal -offset indent -compact +uudecode temp +tar xzf src_tree.tgz +.Ed +.Sh SEE ALSO +.Xr gzip 1 , +.Xr mail 1 , +.Xr tar 1 , +.\".Xr uucp 1 , +.Xr uuencode 5 +.Sh STANDARDS +The +.Nm uudecode +and +.Nm +utilities conform to +.St -p1003.1-2008 . +.Sh HISTORY +The +.Nm uudecode +and +.Nm +utilities appeared in +.Bx 4.0 . +.Sh SECURITY CONSIDERATIONS +When using +.Nm uudecode +with files coming from dubious sources, +always either explicitly pass the +.Fl o +option or check the header (the first line) of the encoded file for +safety. +Blindly using a +.Ar headername +from a hostile source can overwrite important files. diff --git a/usr.bin/uuencode/uuencode.5 b/usr.bin/uuencode/uuencode.5 new file mode 100644 index 0000000..4c8950a --- /dev/null +++ b/usr.bin/uuencode/uuencode.5 @@ -0,0 +1,145 @@ +.\" $NetBSD: uuencode.5,v 1.12 2016/06/06 15:09:33 abhinav Exp $ +.\" +.\" Copyright (c) 1989, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)uuencode.format.5 8.2 (Berkeley) 1/12/94 +.\" +.Dd June 2, 2016 +.Dt UUENCODE 5 +.Os +.Sh NAME +.Nm uuencode +.Nd format of an encoded uuencode file +.Sh DESCRIPTION +Files output by +.Xr uuencode 1 +consist of a header line, +followed by a number of body lines, +and a trailer line. +The +.Xr uudecode 1 +command +will ignore any lines preceding the header or +following the trailer. +Lines preceding a header must not, of course, +look like a header. +.Pp +The header line starts with the word +.Dq begin , +a space, +a file mode (in octal), +a space, +and finally a string which names the file being encoded. +.Pp +The central engine of +.Xr uuencode 1 +is a six-bit encoding function which outputs an +.Tn ASCII +character. +The six bits to be encoded are treated as a small integer and added +with the +.Tn ASCII +value for the space character (octal 40). +The result is a printable +.Tn ASCII +character. +In the case where all six bits to be encoded are zero, +the +.Tn ASCII +backquote character \` (octal 140) is emitted instead of what +would normally be a space. +.Pp +The body of an encoded file consists of one or more lines, +each of which may be a maximum of 86 characters long (including the trailing +newline). +Each line represents an encoded chunk of data from the input file and begins +with a byte count, +followed by encoded bytes, +followed by a newline. +.Pp +The byte count is a six-bit integer encoded with the above function, +representing the number of bytes encoded in the rest of the line. +The method used to encode the data expands its size by +133% (described below). +Therefore it is important to note that the byte count describes the size of +the chunk of data before it is encoded, not afterwards. +The six bit size of this number effectively limits the number of bytes +that can be encoded in each line to a maximum of 63. +While +.Xr uuencode 1 +will not encode more than 45 bytes per line, +.Xr uudecode 1 +will tolerate the maximum line size. +.Pp +The remaining characters in the line represent the data of the input +file encoded as follows. +Input data are broken into groups of three eight-bit bytes, +which are then interpreted together as a 24-bit block. +The first bit of the block is the highest order bit of the first character, +and the last is the lowest order bit of the third character. +This block is then broken into four six-bit integers which are encoded one by +one starting from the first bit of the block. +The result is a four character +.Tn ASCII +string for every three bytes of input data. +.Pp +Encoded lines of data continue in this manner until the input file is +exhausted. +The end of the body is signaled by an encoded line with a byte count +of zero (the +.Tn ASCII +backquote character \`). +.Pp +Obviously, not every input file will be a multiple of three bytes in size. +In these cases, +.Xr uuencode 1 +will pad the remaining one or two bytes of data with garbage bytes until +a three byte group is created. +The byte count in a line containing +garbage padding will reflect the actual number of bytes encoded, making +it possible to convey how many bytes are garbage. +.Pp +The trailer line consists of +.Dq end +on a line by itself. +.Sh SEE ALSO +.Xr mail 1 , +.Xr uudecode 1 , +.Xr uuencode 1 , +.Xr ascii 7 +.Sh HISTORY +The +.Nm +file format appeared in +.Bx 4.0 . +.Sh BUGS +The interpretation of the +.Nm +format relies on properties of the +.Tn ASCII +character set and may not work correctly on non-ASCII systems. diff --git a/usr.bin/uuencode/uuencode.c b/usr.bin/uuencode/uuencode.c new file mode 100644 index 0000000..50b7051 --- /dev/null +++ b/usr.bin/uuencode/uuencode.c @@ -0,0 +1,202 @@ +/* $NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $ */ + +/*- + * Copyright (c) 1983, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)uuencode.c 8.2 (Berkeley) 4/2/94"; +#else +__RCSID("$NetBSD: uuencode.c,v 1.16 2014/09/06 18:58:35 dholland Exp $"); +#endif +#endif /* not lint */ + +/* + * uuencode [input] output + * + * Encode a file so it can be mailed to a remote system. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void encode(void); +static void base64_encode(void); +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + struct stat sb; + int base64, ch, mode; + + mode = 0; + base64 = 0; + setlocale(LC_ALL, ""); + setprogname(argv[0]); + + while ((ch = getopt(argc, argv, "m")) != -1) { + switch(ch) { + case 'm': + base64 = 1; + break; + default: + usage(); + } + } + argv += optind; + argc -= optind; + + switch(argc) { + case 2: /* optional first argument is input file */ + if (!freopen(*argv, "r", stdin) || fstat(fileno(stdin), &sb)) + err(1, "%s", *argv); +#define RWX (S_IRWXU|S_IRWXG|S_IRWXO) + mode = sb.st_mode & RWX; + ++argv; + break; + case 1: +#define RW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) + mode = RW & ~umask(RW); + break; + case 0: + default: + usage(); + } + + if (base64) { + (void)printf("begin-base64 %o %s\n", mode, *argv); + base64_encode(); + (void)printf("====\n"); + } else { + (void)printf("begin %o %s\n", mode, *argv); + encode(); + (void)printf("end\n"); + } + + if (ferror(stdout)) + err(1, "write error"); + exit(0); +} + +/* ENC is the basic 1 character encoding function to make a char printing */ +#define ENC(c) ((c) ? ((c) & 077) + ' ': '`') + +/* + * copy from in to out, encoding in base64 as you go along. + */ +static void +base64_encode(void) +{ + /* + * Output must fit into 80 columns, chunks come in 4, leave 1. + */ +#define GROUPS ((70 / 4) - 1) + unsigned char buf[3]; + char buf2[sizeof(buf) * 2 + 1]; + size_t n; + int rv, sequence; + + sequence = 0; + + while ((n = fread(buf, 1, sizeof(buf), stdin))) { + ++sequence; + rv = b64_ntop(buf, n, buf2, (sizeof(buf2) / sizeof(buf2[0]))); + if (rv == -1) + errx(1, "b64_ntop: error encoding base64"); + printf("%s%s", buf2, (sequence % GROUPS) ? "" : "\n"); + } + if (sequence % GROUPS) + printf("\n"); +} + +/* + * copy from in to out, encoding as you go along. + */ +static void +encode(void) +{ + int ch, n; + char *p; + char buf[80]; + + while ((n = fread(buf, 1, 45, stdin)) > 0) { + ch = ENC(n); + if (putchar(ch) == EOF) + break; + for (p = buf; n > 0; n -= 3, p += 3) { + ch = *p >> 2; + ch = ENC(ch); + if (putchar(ch) == EOF) + break; + ch = ((*p << 4) & 060) | ((p[1] >> 4) & 017); + ch = ENC(ch); + if (putchar(ch) == EOF) + break; + ch = ((p[1] << 2) & 074) | ((p[2] >> 6) & 03); + ch = ENC(ch); + if (putchar(ch) == EOF) + break; + ch = p[2] & 077; + ch = ENC(ch); + if (putchar(ch) == EOF) + break; + } + if (putchar('\n') == EOF) + break; + } + if (ferror(stdin)) + err(1, "read error."); + ch = ENC('\0'); + (void)putchar(ch); + (void)putchar('\n'); +} + +static void +usage(void) +{ + (void)fprintf(stderr, + "usage: %s [-m] [inputfile] headername > encodedfile\n", + getprogname()); + exit(1); +} diff --git a/usr.bin/wc/wc.1 b/usr.bin/wc/wc.1 new file mode 100644 index 0000000..60454a3 --- /dev/null +++ b/usr.bin/wc/wc.1 @@ -0,0 +1,146 @@ +.\" $NetBSD: wc.1,v 1.17 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" the Institute of Electrical and Electronics Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)wc.1 8.2 (Berkeley) 4/19/94 +.\" +.Dd February 18, 2010 +.Dt WC 1 +.Os +.Sh NAME +.Nm wc +.Nd word, line, and byte count +.Sh SYNOPSIS +.Nm +.Op Fl c | Fl m +.Op Fl Llw +.Op Ar file ... +.Sh DESCRIPTION +The +.Nm +utility displays the number of lines, words, bytes and characters contained in each +input +.Ar file +(or standard input, by default) to the standard output. +A line is defined as a string of characters delimited by a +character, +and a word is defined as a string of characters delimited by white space +characters. +White space characters are the set of characters for which the +.Xr iswspace 3 +function returns true. +If more than one input file is specified, a line of cumulative counts +for all the files is displayed on a separate line after the output for +the last file. +.Pp +The following options are available: +.Bl -tag -width Ds +.It Fl c +The number of bytes in each input file +is written to the standard output. +.It Fl L +The number of characters in the longest line of each input file +is written to the standard output. +.It Fl l +The number of lines in each input file +is written to the standard output. +.It Fl m +The number of characters in each input file +is written to the standard output. +.It Fl w +The number of words in each input file +is written to the standard output. +.El +.Pp +When an option is specified, +.Nm +only +reports the +information requested by that option. +The default action is equivalent to all the flags +.Fl clw +having been specified. +.Pp +The following operands are available: +.Bl -tag -width Ds +.It Ar file +A pathname of an input file. +.El +.Pp +If no file names +are specified, the standard input is used and +no file name is displayed. +.Pp +By default, the standard output contains a line for each +input file of the form: +.Bd -literal -offset indent +lines words bytes file_name +.Ed +.Sh EXIT STATUS +.Ex -std wc +.Sh COMPATIBILITY +Historically, the +.Nm +utility was documented to define a word as a ``maximal string of +characters delimited by +.Aq space , +.Aq tab +or +.Aq newline +characters''. +The implementation, however, didn't handle non-printing characters +correctly so that `` ^D^E '' counted as 6 spaces, while ``foo^D^Ebar'' +counted as 8 characters. +.Bx 4 +systems after +.Bx 4.3 +modified the implementation to be consistent +with the documentation. +This implementation defines a ``word'' in terms of the +.Xr iswspace 3 +function, as required by +.St -p1003.2 . +.Pp +The +.Fl L +option is a non-standard extension, compatible with the +.Fl L +option of the GNU and +.Fx +.Nm +utilities. +.Sh SEE ALSO +.Xr iswspace 3 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -p1003.2-92 . diff --git a/usr.bin/wc/wc.c b/usr.bin/wc/wc.c new file mode 100644 index 0000000..602a450 --- /dev/null +++ b/usr.bin/wc/wc.c @@ -0,0 +1,354 @@ +/* $NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $ */ + +/* + * Copyright (c) 1980, 1987, 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1987, 1991, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)wc.c 8.2 (Berkeley) 5/2/95"; +#else +__RCSID("$NetBSD: wc.c,v 1.35 2011/09/16 15:39:30 joerg Exp $"); +#endif +#endif /* not lint */ + +/* wc line, word, char count and optionally longest line. */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef NO_QUAD +typedef u_long wc_count_t; +# define WCFMT " %7lu" +# define WCCAST unsigned long +#else +typedef u_quad_t wc_count_t; +# define WCFMT " %7llu" +# define WCCAST unsigned long long +#endif + +static wc_count_t tlinect, twordct, tcharct, tlongest; +static bool doline, doword, dobyte, dochar, dolongest; +static int rval = 0; + +static void cnt(const char *); +static void print_counts(wc_count_t, wc_count_t, wc_count_t, wc_count_t, + const char *); +__dead static void usage(void); +static size_t do_mb(wchar_t *, const char *, size_t, mbstate_t *, + size_t *, const char *); + +int +main(int argc, char *argv[]) +{ + int ch; + + setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "lwcmL")) != -1) + switch (ch) { + case 'l': + doline = true; + break; + case 'w': + doword = true; + break; + case 'm': + dochar = true; + dobyte = 0; + break; + case 'c': + dochar = 0; + dobyte = true; + break; + case 'L': + dolongest = true; + break; + case '?': + default: + usage(); + } + argv += optind; + argc -= optind; + + /* Wc's flags are on by default. */ + if (!(doline || doword || dobyte || dochar || dolongest)) + doline = doword = dobyte = true; + + if (*argv == NULL) { + cnt(NULL); + } else { + bool dototal = (argc > 1); + + do { + cnt(*argv); + } while(*++argv); + + if (dototal) { + print_counts(tlinect, twordct, tcharct, tlongest, + "total"); + } + } + + exit(rval); +} + +static size_t +do_mb(wchar_t *wc, const char *p, size_t len, mbstate_t *st, + size_t *retcnt, const char *file) +{ + size_t r; + size_t c = 0; + + do { + r = mbrtowc(wc, p, len, st); + if (r == (size_t)-1) { + warnx("%s: invalid byte sequence", file); + rval = 1; + + /* XXX skip 1 byte */ + len--; + p++; + memset(st, 0, sizeof(*st)); + continue; + } else if (r == (size_t)-2) + break; + else if (r == 0) + r = 1; + c++; + if (wc) + wc++; + len -= r; + p += r; + } while (len > 0); + + *retcnt = c; + + return (r); +} + +static void +cnt(const char *file) +{ + u_char buf[MAXBSIZE]; + wchar_t wbuf[MAXBSIZE]; + struct stat sb; + wc_count_t charct, linect, wordct, longest; + mbstate_t st; + u_char *C; + wchar_t *WC; + const char *name; /* filename or */ + size_t r = 0; + int fd, len = 0; + + linect = wordct = charct = longest = 0; + if (file != NULL) { + if ((fd = open(file, O_RDONLY, 0)) < 0) { + warn("%s", file); + rval = 1; + return; + } + name = file; + } else { + fd = STDIN_FILENO; + name = ""; + } + + if (dochar || doword || dolongest) + (void)memset(&st, 0, sizeof(st)); + + if (!(doword || dolongest)) { + /* + * line counting is split out because it's a lot + * faster to get lines than to get words, since + * the word count requires some logic. + */ + if (doline || dochar) { + while ((len = read(fd, buf, MAXBSIZE)) > 0) { + if (dochar) { + size_t wlen; + + r = do_mb(0, (char *)buf, (size_t)len, + &st, &wlen, name); + charct += wlen; + } else if (dobyte) + charct += len; + if (doline) { + for (C = buf; len--; ++C) { + if (*C == '\n') + ++linect; + } + } + } + } + + /* + * if all we need is the number of characters and + * it's a directory or a regular or linked file, just + * stat the puppy. We avoid testing for it not being + * a special device in case someone adds a new type + * of inode. + */ + else if (dobyte) { + if (fstat(fd, &sb)) { + warn("%s", name); + rval = 1; + } else { + if (S_ISREG(sb.st_mode) || + S_ISLNK(sb.st_mode) || + S_ISDIR(sb.st_mode)) { + charct = sb.st_size; + } else { + while ((len = + read(fd, buf, MAXBSIZE)) > 0) + charct += len; + } + } + } + } else { + /* do it the hard way... */ + wc_count_t linelen; + bool gotsp; + + linelen = 0; + gotsp = true; + while ((len = read(fd, buf, MAXBSIZE)) > 0) { + size_t wlen; + + r = do_mb(wbuf, (char *)buf, (size_t)len, &st, &wlen, + name); + if (dochar) { + charct += wlen; + } else if (dobyte) { + charct += len; + } + for (WC = wbuf; wlen--; ++WC) { + if (iswspace(*WC)) { + gotsp = true; + if (*WC == L'\n') { + ++linect; + if (linelen > longest) + longest = linelen; + linelen = 0; + } else { + linelen++; + } + } else { + /* + * This line implements the POSIX + * spec, i.e. a word is a "maximal + * string of characters delimited by + * whitespace." Notice nothing was + * said about a character being + * printing or non-printing. + */ + if (gotsp) { + gotsp = false; + ++wordct; + } + + linelen++; + } + } + } + } + + if (len == -1) { + warn("%s", name); + rval = 1; + } + if (dochar && r == (size_t)-2) { + warnx("%s: incomplete multibyte character", name); + rval = 1; + } + + print_counts(linect, wordct, charct, longest, file); + + /* + * don't bother checkint doline, doword, or dobyte --- speeds + * up the common case + */ + tlinect += linect; + twordct += wordct; + tcharct += charct; + if (dolongest && longest > tlongest) + tlongest = longest; + + if (close(fd)) { + warn("%s", name); + rval = 1; + } +} + +static void +print_counts(wc_count_t lines, wc_count_t words, wc_count_t chars, + wc_count_t longest, const char *name) +{ + + if (doline) + (void)printf(WCFMT, (WCCAST)lines); + if (doword) + (void)printf(WCFMT, (WCCAST)words); + if (dobyte || dochar) + (void)printf(WCFMT, (WCCAST)chars); + if (dolongest) + (void)printf(WCFMT, (WCCAST)longest); + + if (name != NULL) + (void)printf(" %s\n", name); + else + (void)putchar('\n'); +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: wc [-c | -m] [-Llw] [file ...]\n"); + exit(1); +} diff --git a/usr.bin/what/what.1 b/usr.bin/what/what.1 new file mode 100644 index 0000000..4a96a7a --- /dev/null +++ b/usr.bin/what/what.1 @@ -0,0 +1,82 @@ +.\" $NetBSD: what.1,v 1.15 2017/07/03 21:34:22 wiz Exp $ +.\" +.\" Copyright (c) 1980, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)what.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd December 12, 2015 +.Dt WHAT 1 +.Os +.Sh NAME +.Nm what +.Nd search files for SCCS identifiers +.Sh SYNOPSIS +.Nm +.Op Fl s +.Ar +.Sh DESCRIPTION +The +.Nm +utility reads each +.Ar file +operand and searches for sequences of the form +.Dq \&@(#) +as inserted by SCCS and SCCS-compatible version control systems. +It prints the remainder of the string following this marker, up to +a null character, newline, double quote, backslash, or +.Dq \&> +character. +.Pp +If the +.Fl s +option is specified, only the first occurrence of an identification string in +each file is printed. +.Sh EXIT STATUS +The +.Nm +utility exits 0 if any matches were found, and 1 otherwise. +.Sh STANDARDS +The +.Nm +utility conforms to +.St -xpg4.2 . +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.0 . +.Sh BUGS +As +.Bx +is not licensed to distribute +.Tn SCCS +this is a rewrite of the +.Nm +command which is part of +.Tn SCCS , +and may not behave exactly the same as that command does. diff --git a/usr.bin/what/what.c b/usr.bin/what/what.c new file mode 100644 index 0000000..fb29e52 --- /dev/null +++ b/usr.bin/what/what.c @@ -0,0 +1,127 @@ +/* $NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $ */ + +/* + * Copyright (c) 1980, 1988, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1980, 1988, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)what.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: what.c,v 1.12 2015/12/12 09:50:12 dholland Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include + +static void search(void); +__dead static void usage(void); + +static int matches; +static int sflag; + +/* + * what + */ +int +main(int argc, char **argv) +{ + int c; + + (void)setlocale(LC_ALL, ""); + + matches = sflag = 0; + while ((c = getopt(argc, argv, "s")) != -1) { + switch (c) { + case 's': + sflag = 1; + break; + case '?': + default: + usage(); + } + } + argc -= optind; + argv += optind; + + if (argc < 1) { + usage(); + } else do { + if (freopen(*argv, "r", stdin) == NULL) { + perror(*argv); + exit(matches ? EXIT_SUCCESS : 1); + } + printf("%s\n", *argv); + search(); + } while (*++argv != NULL); + + /* Note: the standard explicitly specifies an exit status of 1. */ + exit(matches ? EXIT_SUCCESS : 1); + /* NOTREACHED */ +} + +static void +search(void) +{ + int c; + + while ((c = getchar()) != EOF) { +loop: if (c != '@') + continue; + if ((c = getchar()) != '(') + goto loop; + if ((c = getchar()) != '#') + goto loop; + if ((c = getchar()) != ')') + goto loop; + putchar('\t'); + while ((c = getchar()) != EOF && c != '\0' && c != '"' && + c != '>' && c != '\n' && c != '\\') + putchar(c); + putchar('\n'); + matches++; + if (sflag) + break; + } +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: what [-s] file ...\n"); + exit(1); +} diff --git a/usr.bin/who/utmpentry.c b/usr.bin/who/utmpentry.c new file mode 100644 index 0000000..c74397b --- /dev/null +++ b/usr.bin/who/utmpentry.c @@ -0,0 +1,329 @@ +/* $NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $ */ + +/*- + * Copyright (c) 2002 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: utmpentry.c,v 1.18 2015/11/21 15:01:43 christos Exp $"); +#endif + +#include + +#include +#include +#include +#include + +#ifdef SUPPORT_UTMP +#include +#endif +#ifdef SUPPORT_UTMPX +#include +#endif + +#include "utmpentry.h" + + +/* Fail the compile if x is not true, by constructing an illegal type. */ +#define COMPILE_ASSERT(x) /*LINTED null effect */ \ + ((void)sizeof(struct { unsigned : ((x) ? 1 : -1); })) + + +#ifdef SUPPORT_UTMP +static void getentry(struct utmpentry *, struct utmp *); +static struct timespec utmptime = {0, 0}; +#endif +#ifdef SUPPORT_UTMPX +static void getentryx(struct utmpentry *, struct utmpx *); +static struct timespec utmpxtime = {0, 0}; +#endif +#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP) +static int setup(const char *); +static void adjust_size(struct utmpentry *e); +#endif + +size_t maxname = 8, maxline = 8, maxhost = 16; +int etype = 1 << USER_PROCESS; +static size_t numutmp = 0; +static struct utmpentry *ehead; + +#if defined(SUPPORT_UTMPX) || defined(SUPPORT_UTMP) +static void +adjust_size(struct utmpentry *e) +{ + size_t max; + + if ((max = strlen(e->name)) > maxname) + maxname = max; + if ((max = strlen(e->line)) > maxline) + maxline = max; + if ((max = strlen(e->host)) > maxhost) + maxhost = max; +} + +static int +setup(const char *fname) +{ + int what = 3; + struct stat st; + const char *sfname; + + if (fname == NULL) { +#ifdef SUPPORT_UTMPX + setutxent(); +#endif +#ifdef SUPPORT_UTMP + setutent(); +#endif + } else { + size_t len = strlen(fname); + if (len == 0) + errx(1, "Filename cannot be 0 length."); + what = fname[len - 1] == 'x' ? 1 : 2; + if (what == 1) { +#ifdef SUPPORT_UTMPX + if (utmpxname(fname) == 0) + warnx("Cannot set utmpx file to `%s'", + fname); +#else + warnx("utmpx support not compiled in"); +#endif + } else { +#ifdef SUPPORT_UTMP + if (utmpname(fname) == 0) + warnx("Cannot set utmp file to `%s'", + fname); +#else + warnx("utmp support not compiled in"); +#endif + } + } +#ifdef SUPPORT_UTMPX + if (what & 1) { + sfname = fname ? fname : _PATH_UTMPX; + if (stat(sfname, &st) == -1) { + warn("Cannot stat `%s'", sfname); + what &= ~1; + } else { + if (timespeccmp(&st.st_mtimespec, &utmpxtime, >)) + utmpxtime = st.st_mtimespec; + else + what &= ~1; + } + } +#endif +#ifdef SUPPORT_UTMP + if (what & 2) { + sfname = fname ? fname : _PATH_UTMP; + if (stat(sfname, &st) == -1) { + warn("Cannot stat `%s'", sfname); + what &= ~2; + } else { + if (timespeccmp(&st.st_mtimespec, &utmptime, >)) + utmptime = st.st_mtimespec; + else + what &= ~2; + } + } +#endif + return what; +} +#endif + +void +endutentries(void) +{ + struct utmpentry *ep; + +#ifdef SUPPORT_UTMP + timespecclear(&utmptime); +#endif +#ifdef SUPPORT_UTMPX + timespecclear(&utmpxtime); +#endif + ep = ehead; + while (ep) { + struct utmpentry *sep = ep; + ep = ep->next; + free(sep); + } + ehead = NULL; + numutmp = 0; +} + +size_t +getutentries(const char *fname, struct utmpentry **epp) +{ +#ifdef SUPPORT_UTMPX + struct utmpx *utx; +#endif +#ifdef SUPPORT_UTMP + struct utmp *ut; +#endif +#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX) + struct utmpentry *ep; + int what = setup(fname); + struct utmpentry **nextp = &ehead; + switch (what) { + case 0: + /* No updates */ + *epp = ehead; + return numutmp; + default: + /* Need to re-scan */ + ehead = NULL; + numutmp = 0; + } +#endif + +#ifdef SUPPORT_UTMPX + while ((what & 1) && (utx = getutxent()) != NULL) { + if (fname == NULL && ((1 << utx->ut_type) & etype) == 0) + continue; + if ((ep = calloc(1, sizeof(struct utmpentry))) == NULL) { + warn(NULL); + return 0; + } + getentryx(ep, utx); + *nextp = ep; + nextp = &(ep->next); + } +#endif + +#ifdef SUPPORT_UTMP + if ((etype & (1 << USER_PROCESS)) != 0) { + while ((what & 2) && (ut = getutent()) != NULL) { + if (fname == NULL && (*ut->ut_name == '\0' || + *ut->ut_line == '\0')) + continue; + /* Don't process entries that we have utmpx for */ + for (ep = ehead; ep != NULL; ep = ep->next) { + if (strncmp(ep->line, ut->ut_line, + sizeof(ut->ut_line)) == 0) + break; + } + if (ep != NULL) + continue; + if ((ep = calloc(1, sizeof(*ep))) == NULL) { + warn(NULL); + return 0; + } + getentry(ep, ut); + *nextp = ep; + nextp = &(ep->next); + } + } +#endif + numutmp = 0; +#if defined(SUPPORT_UTMP) || defined(SUPPORT_UTMPX) + if (ehead != NULL) { + struct utmpentry *from = ehead, *save; + + ehead = NULL; + while (from != NULL) { + for (nextp = &ehead; + (*nextp) && strcmp(from->line, (*nextp)->line) > 0; + nextp = &(*nextp)->next) + continue; + save = from; + from = from->next; + save->next = *nextp; + *nextp = save; + numutmp++; + } + } + *epp = ehead; + return numutmp; +#else + *epp = NULL; + return 0; +#endif +} + +#ifdef SUPPORT_UTMP +static void +getentry(struct utmpentry *e, struct utmp *up) +{ + COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name)); + COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line)); + COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host)); + + /* + * e has just been calloc'd. We don't need to clear it or + * append null-terminators, because its length is strictly + * greater than the source string. Use strncpy to _read_ + * up->ut_* because they may not be terminated. For this + * reason we use the size of the _source_ as the length + * argument. + */ + (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name)); + (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line)); + (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host)); + + e->tv.tv_sec = up->ut_time; + e->tv.tv_usec = 0; + e->pid = 0; + e->term = 0; + e->exit = 0; + e->sess = 0; + e->type = USER_PROCESS; + adjust_size(e); +} +#endif + +#ifdef SUPPORT_UTMPX +static void +getentryx(struct utmpentry *e, struct utmpx *up) +{ + COMPILE_ASSERT(sizeof(e->name) > sizeof(up->ut_name)); + COMPILE_ASSERT(sizeof(e->line) > sizeof(up->ut_line)); + COMPILE_ASSERT(sizeof(e->host) > sizeof(up->ut_host)); + + /* + * e has just been calloc'd. We don't need to clear it or + * append null-terminators, because its length is strictly + * greater than the source string. Use strncpy to _read_ + * up->ut_* because they may not be terminated. For this + * reason we use the size of the _source_ as the length + * argument. + */ + (void)strncpy(e->name, up->ut_name, sizeof(up->ut_name)); + (void)strncpy(e->line, up->ut_line, sizeof(up->ut_line)); + (void)strncpy(e->host, up->ut_host, sizeof(up->ut_host)); + + e->tv = up->ut_tv; + e->pid = up->ut_pid; + e->term = up->ut_exit.e_termination; + e->exit = up->ut_exit.e_exit; + e->sess = up->ut_session; + e->type = up->ut_type; + adjust_size(e); +} +#endif diff --git a/usr.bin/who/utmpentry.h b/usr.bin/who/utmpentry.h new file mode 100644 index 0000000..263a33a --- /dev/null +++ b/usr.bin/who/utmpentry.h @@ -0,0 +1,76 @@ +/* $NetBSD: utmpentry.h,v 1.8 2015/11/21 15:01:43 christos Exp $ */ + +/*- + * Copyright (c) 2002 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Christos Zoulas. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#if defined(SUPPORT_UTMPX) +# include +# define WHO_NAME_LEN _UTX_USERSIZE +# define WHO_LINE_LEN _UTX_LINESIZE +# define WHO_HOST_LEN _UTX_HOSTSIZE +#elif defined(SUPPORT_UTMP) +# include +# define WHO_NAME_LEN UT_NAMESIZE +# define WHO_LINE_LEN UT_LINESIZE +# define WHO_HOST_LEN UT_HOSTSIZE +#else +# error Either SUPPORT_UTMPX or SUPPORT_UTMP must be defined! +#endif + + +struct utmpentry { + char name[WHO_NAME_LEN + 1]; + char line[WHO_LINE_LEN + 1]; + char host[WHO_HOST_LEN + 1]; + struct timeval tv; + pid_t pid; + uint16_t term; + uint16_t exit; + uint16_t sess; + uint16_t type; + struct utmpentry *next; +}; + +extern size_t maxname, maxline, maxhost; +extern int etype; + +/* + * getutentries provides a linked list of struct utmpentry and returns + * the number of entries. The first argument, if not null, names an + * alternate utmp(x) file to look in. + * + * The memory returned by getutentries belongs to getutentries. The + * list returned (or elements of it) may be returned again later if + * utmp hasn't changed in the meantime. + * + * endutentries clears and frees the cached data. + */ + +size_t getutentries(const char *, struct utmpentry **); +void endutentries(void); diff --git a/usr.bin/who/who.1 b/usr.bin/who/who.1 new file mode 100644 index 0000000..3873b47 --- /dev/null +++ b/usr.bin/who/who.1 @@ -0,0 +1,197 @@ +.\" $NetBSD: who.1,v 1.24 2018/05/11 16:36:57 sevan Exp $ +.\" +.\" Copyright (c) 1986, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)who.1 8.2 (Berkeley) 12/30/93 +.\" +.Dd May 11, 2018 +.Dt WHO 1 +.Os +.Sh NAME +.Nm who +.Nd display who is logged in +.Sh SYNOPSIS +.Nm +.Op Fl abdHlmqrsTtuv +.Op Ar file +.Nm +.Ar am i +.Sh DESCRIPTION +The +.Nm +utility displays a list of all users currently logged on, showing for +each user the login name, tty name, the date and time of login, and +hostname if not local. +.Pp +Available options: +.Bl -tag -width file +.It Fl a +Same as +.Fl -bdlprTtuv . +.It Fl b +Time of last system boot. +.It Fl d +Print dead processes. +.It Fl H +Write column headings above the regular output. +.It Fl l +Print system login processes. +.It Fl m +Only print information about the current terminal. +This is the POSIX way of saying +.Nm +.Ar am i . +.It Fl p +Print active processes spawned by +.Xr init 8 . +.It Fl q +.Dq Quick mode : +List only the names and the number of users currently logged on. +When this option is used, all other options are ignored. +.It Fl r +Print the current runlevel. +Supported runlevels are: +.Bl -tag -width "s (SINGLE_USER)" +.It Dv d Pq Dv DEATH +The system has halted. +.It Dv s Pq Dv SINGLE_USER +The system is running in single user mode. +.It Dv r Pq Dv RUNCOM +The system is executing +.Pa /etc/rc . +.It Dv t Pq Dv READ_TTYS +The system is processing +.Pa /etc/ttys . +.It Dv m Pq Dv MULTI_USER +The system is running in multi-user mode. +.It Dv T Pq Dv CLEAN_TTYS +The system is in the process of stopping processes +associated with terminal devices. +.It Dv c Pq Dv CATATONIA +The system is in the process of shutting down and will +not create new processes. +.El +.It Fl s +List only the name, line and time fields. +This is the default. +.It Fl T +Print a character after the user name indicating the state of the +terminal line: +.Sq + +if the terminal is writable; +.Sq - +if it is not; +and +.Sq \&? +if a bad line is encountered. +.It Fl t +Print last system clock change. +.It Fl u +Print the idle time for each user, and the associated process ID. +.It Fl v +When printing of more information is requested with +.Fl u , +this switch can be used to also printed +process termination signals, +process exit status, +session id for windowing +and the type of the entry, see documentation of ut_type in +.Xr getutxent 3 . +.It Ar \&am I +Returns the invoker's real user name. +.It Ar file +By default, +.Nm +gathers information from the file +.Pa /var/run/utmpx . +An alternative +.Ar file +may be specified which is usually +.Pa /var/log/wtmpx +(or +.Pa /var/log/wtmp , +or +.Pa /var/log/wtmpx.[0-6] +or +.Pa /var/log/wtmp.[0-6] +depending on site policy as +.Pa wtmpx +can grow quite large and daily versions may or may not +be kept around after compression by +.Xr ac 8 ) . +The +.Pa wtmpx +and +.Pa wtmp +file contains a record of every login, logout, +crash, shutdown and date change +since +.Pa wtmpx +and +.Pa wtmp +were last truncated or +created. +.El +.Pp +If +.Pa /var/log/wtmpx +or +.Pa /var/log/wtmp +are being used as the file, the user name may be empty +or one of the special characters '|', '}' and '~'. +Logouts produce an output line without any user name. +For more information on the +special characters, see +.Xr utmp 5 . +.Sh FILES +.Bl -tag -width /var/log/wtmp.[0-6] -compact +.It Pa /var/run/utmp +.It Pa /var/run/utmpx +.It Pa /var/log/wtmp +.It Pa /var/log/wtmp.[0-6] +.It Pa /var/log/wtmpx +.It Pa /var/log/wtmpx.[0-6] +.El +.Sh SEE ALSO +.Xr last 1 , +.Xr mesg 1 , +.Xr users 1 , +.Xr getuid 2 , +.Xr utmp 5 , +.Xr utmpx 5 +.Sh STANDARDS +The +.Nm +utility is expected to conform to +.St -p1003.2-92 . +.Sh HISTORY +A +.Nm +utility appeared in +.At v1 : +.Lk http://cm.bell-labs.com/cm/cs/who/dmr/pdfs/man14.pdf diff --git a/usr.bin/who/who.c b/usr.bin/who/who.c new file mode 100644 index 0000000..f0c6564 --- /dev/null +++ b/usr.bin/who/who.c @@ -0,0 +1,391 @@ +/* $NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Michael Fischbein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)who.c 8.1 (Berkeley) 6/6/93"; +#endif +__RCSID("$NetBSD: who.c,v 1.25 2015/11/21 15:01:43 christos Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef SUPPORT_UTMP +#include +#endif +#ifdef SUPPORT_UTMPX +#include +#endif + +#include "utmpentry.h" + +static void output_labels(void); +static void who_am_i(const char *, int); +static void usage(void) __dead; +static void process(const char *, int); +static void eprint(const struct utmpentry *); +static void print(const char *, const char *, time_t, const char *, pid_t pid, + uint16_t term, uint16_t xit, uint16_t sess, uint16_t type); +static void quick(const char *); + +static int show_term; /* show term state */ +static int show_idle; /* show idle time */ +static int show_details; /* show exit status etc. */ + +struct ut_type_names { + int type; + const char *name; +} ut_type_names[] = { +#ifdef SUPPORT_UTMPX + { EMPTY, "empty" }, + { RUN_LVL, "run level" }, + { BOOT_TIME, "boot time" }, + { OLD_TIME, "old time" }, + { NEW_TIME, "new time" }, + { INIT_PROCESS, "init process" }, + { LOGIN_PROCESS, "login process" }, + { USER_PROCESS, "user process" }, + { DEAD_PROCESS, "dead process" }, +#if defined(_NETBSD_SOURCE) + { ACCOUNTING, "accounting" }, + { SIGNATURE, "signature" }, + { DOWN_TIME, "down time" }, +#endif /* _NETBSD_SOURCE */ +#endif /* SUPPORT_UTMPX */ + { -1, "unknown" } +}; + +int +main(int argc, char *argv[]) +{ + int c, only_current_term, show_labels, quick_mode, default_mode; + int et = 0; + + setlocale(LC_ALL, ""); + + only_current_term = show_term = show_idle = show_labels = 0; + quick_mode = default_mode = 0; + + while ((c = getopt(argc, argv, "abdHlmpqrsTtuv")) != -1) { + switch (c) { + case 'a': + et = -1; + show_idle = show_details = 1; + break; + case 'b': + et |= (1 << BOOT_TIME); + break; + case 'd': + et |= (1 << DEAD_PROCESS); + break; + case 'H': + show_labels = 1; + break; + case 'l': + et |= (1 << LOGIN_PROCESS); + break; + case 'm': + only_current_term = 1; + break; + case 'p': + et |= (1 << INIT_PROCESS); + break; + case 'q': + quick_mode = 1; + break; + case 'r': + et |= (1 << RUN_LVL); + break; + case 's': + default_mode = 1; + break; + case 'T': + show_term = 1; + break; + case 't': + et |= (1 << NEW_TIME); + break; + case 'u': + show_idle = 1; + break; + case 'v': + show_details = 1; + break; + default: + usage(); + /* NOTREACHED */ + } + } + argc -= optind; + argv += optind; + + if (et != 0) + etype = et; + + if (chdir("/dev")) { + err(EXIT_FAILURE, "cannot change directory to /dev"); + /* NOTREACHED */ + } + + if (default_mode) + only_current_term = show_term = show_idle = 0; + + switch (argc) { + case 0: /* who */ + if (quick_mode) { + quick(NULL); + } else if (only_current_term) { + who_am_i(NULL, show_labels); + } else { + process(NULL, show_labels); + } + break; + case 1: /* who utmp_file */ + if (quick_mode) { + quick(*argv); + } else if (only_current_term) { + who_am_i(*argv, show_labels); + } else { + process(*argv, show_labels); + } + break; + case 2: /* who am i */ + who_am_i(NULL, show_labels); + break; + default: + usage(); + /* NOTREACHED */ + } + + return 0; +} + +static char * +strrstr(const char *str, const char *pat) +{ + const char *estr; + size_t len; + if (*pat == '\0') + return __UNCONST(str); + + len = strlen(pat); + + for (estr = str + strlen(str); str < estr; estr--) + if (strncmp(estr, pat, len) == 0) + return __UNCONST(estr); + return NULL; +} + +static void +who_am_i(const char *fname, int show_labels) +{ + struct passwd *pw; + const char *p; + char *t; + time_t now; + struct utmpentry *ehead, *ep; + + /* search through the utmp and find an entry for this tty */ + if ((p = ttyname(STDIN_FILENO)) != NULL) { + + /* strip directory prefixes for ttys */ + if ((t = strrstr(p, "/pts/")) != NULL || + (t = strrchr(p, '/')) != NULL) + p = t + 1; + + (void)getutentries(fname, &ehead); + for (ep = ehead; ep; ep = ep->next) + if (strcmp(ep->line, p) == 0) { + if (show_labels) + output_labels(); + eprint(ep); + return; + } + } else + p = "tty??"; + + (void)time(&now); + pw = getpwuid(getuid()); + if (show_labels) + output_labels(); + print(pw ? pw->pw_name : "?", p, now, "", getpid(), 0, 0, 0, 0); +} + +static void +process(const char *fname, int show_labels) +{ + struct utmpentry *ehead, *ep; + (void)getutentries(fname, &ehead); + if (show_labels) + output_labels(); + for (ep = ehead; ep != NULL; ep = ep->next) + eprint(ep); +} + +static void +eprint(const struct utmpentry *ep) +{ + print(ep->name, ep->line, (time_t)ep->tv.tv_sec, ep->host, ep->pid, + ep->term, ep->exit, ep->sess, ep->type); +} + +static void +print(const char *name, const char *line, time_t t, const char *host, + pid_t pid, uint16_t term, uint16_t xit, uint16_t sess, uint16_t type) +{ + struct stat sb; + char state; + static time_t now = 0; + time_t idle; + const char *types = NULL; + size_t i; + char *tstr; + + state = '?'; + idle = 0; + + for (i = 0; ut_type_names[i].type >= 0; i++) { + types = ut_type_names[i].name; + if (ut_type_names[i].type == type) + break; + } + + if (show_term || show_idle) { + if (now == 0) + time(&now); + + if (stat(line, &sb) == 0) { + state = (sb.st_mode & 020) ? '+' : '-'; + idle = now - sb.st_atime; + } + + } + + (void)printf("%-*.*s ", (int)maxname, (int)maxname, name); + + if (show_term) + (void)printf("%c ", state); + + (void)printf("%-*.*s ", (int)maxline, (int)maxline, line); + tstr = ctime(&t); + (void)printf("%.12s ", tstr ? tstr + 4 : "?"); + + if (show_idle) { + if (idle < 60) + (void)printf(" . "); + else if (idle < (24 * 60 * 60)) + (void)printf("%02ld:%02ld ", + (long)(idle / (60 * 60)), + (long)(idle % (60 * 60)) / 60); + else + (void)printf(" old "); + + (void)printf("\t%6d", pid); + + if (show_details) { + if (type == RUN_LVL) + (void)printf("\tnew=%c old=%c", term, xit); + else + (void)printf("\tterm=%d exit=%d", term, xit); + (void)printf(" sess=%d", sess); + (void)printf(" type=%s ", types); + } + } + + if (*host) + (void)printf("\t(%.*s)", (int)maxhost, host); + (void)putchar('\n'); +} + +static void +output_labels(void) +{ + (void)printf("%-*.*s ", (int)maxname, (int)maxname, "USER"); + + if (show_term) + (void)printf("S "); + + (void)printf("%-*.*s ", (int)maxline, (int)maxline, "LINE"); + (void)printf("WHEN "); + + if (show_idle) { + (void)printf("IDLE "); + (void)printf("\t PID"); + + (void)printf("\tCOMMENT"); + } + + (void)putchar('\n'); +} + +static void +quick(const char *fname) +{ + struct utmpentry *ehead, *ep; + int num = 0; + + (void)getutentries(fname, &ehead); + for (ep = ehead; ep != NULL; ep = ep->next) { + (void)printf("%-*s ", (int)maxname, ep->name); + if ((++num % 8) == 0) + (void)putchar('\n'); + } + if (num % 8) + (void)putchar('\n'); + + (void)printf("# users = %d\n", num); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "Usage: %s [-abdHlmqrsTtuv] [file]\n\t%s am i\n", + getprogname(), getprogname()); + exit(EXIT_FAILURE); +} diff --git a/usr.bin/write/term_chk.c b/usr.bin/write/term_chk.c new file mode 100644 index 0000000..d7d2cbe --- /dev/null +++ b/usr.bin/write/term_chk.c @@ -0,0 +1,137 @@ +/* $NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__RCSID("$NetBSD: term_chk.c,v 1.8 2009/04/14 07:59:17 lukem Exp $"); +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "term_chk.h" + +/* + * term_chk - check that a terminal exists, and get the message bit + * and the access time + */ +int +term_chk(uid_t uid, const char *tty, int *msgsokP, time_t *atimeP, int ismytty, + gid_t saved_egid) +{ + char path[MAXPATHLEN]; + struct stat s; + int i, fd, serrno; + + if (strstr(tty, "../") != NULL) { + errno = EINVAL; + return -1; + } + i = snprintf(path, sizeof path, _PATH_DEV "%s", tty); + if (i < 0 || i >= (int)sizeof(path)) { + errno = ENOMEM; + return -1; + } + + (void)setegid(saved_egid); + fd = open(path, O_WRONLY, 0); + serrno = errno; + (void)setegid(getgid()); + errno = serrno; + + if (fd == -1) + return(-1); + if (fstat(fd, &s) == -1) + goto error; + if (!isatty(fd)) + goto error; + if (s.st_uid != uid && uid != 0) { + errno = EPERM; + goto error; + } + if (msgsokP) + *msgsokP = (s.st_mode & S_IWGRP) != 0; /* group write bit */ + if (atimeP) + *atimeP = s.st_atime; + if (ismytty) + (void)close(fd); + return ismytty ? 0 : fd; +error: + if (fd != -1) { + serrno = errno; + (void)close(fd); + errno = serrno; + } + return -1; +} + +char * +check_sender(time_t *atime, uid_t myuid, gid_t saved_egid) +{ + int myttyfd; + int msgsok; + char *mytty; + + /* check that sender has write enabled */ + if (isatty(fileno(stdin))) + myttyfd = fileno(stdin); + else if (isatty(fileno(stdout))) + myttyfd = fileno(stdout); + else if (isatty(fileno(stderr))) + myttyfd = fileno(stderr); + else if (atime == NULL) + return NULL; + else + errx(1, "Cannot find your tty"); + if ((mytty = ttyname(myttyfd)) == NULL) + err(1, "Cannot find the name of your tty"); + if (strncmp(mytty, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0) + mytty += sizeof(_PATH_DEV) - 1; + if (term_chk(myuid, mytty, &msgsok, atime, 1, saved_egid) == -1) + err(1, "%s%s", _PATH_DEV, mytty); + if (!msgsok) { + warnx( + "You have write permission turned off; no reply possible"); + } + return mytty; +} diff --git a/usr.bin/write/term_chk.h b/usr.bin/write/term_chk.h new file mode 100644 index 0000000..aca4568 --- /dev/null +++ b/usr.bin/write/term_chk.h @@ -0,0 +1,36 @@ +/* $NetBSD: term_chk.h,v 1.2 2003/08/07 11:17:48 agc Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +int term_chk(uid_t, const char *, int *, time_t *, int, gid_t); +char *check_sender(time_t *, uid_t, gid_t); diff --git a/usr.bin/write/write.1 b/usr.bin/write/write.1 new file mode 100644 index 0000000..28d614c --- /dev/null +++ b/usr.bin/write/write.1 @@ -0,0 +1,106 @@ +.\" $NetBSD: write.1,v 1.6 2003/08/07 11:17:48 agc Exp $ +.\" +.\" Copyright (c) 1989, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)write.1 8.1 (Berkeley) 6/6/93 +.\" +.Dd June 6, 1993 +.Dt WRITE 1 +.Os +.Sh NAME +.Nm write +.Nd send a message to another user +.Sh SYNOPSIS +.Nm +.Ar user +.Op Ar ttyname +.Sh DESCRIPTION +.Nm +allows you to communicate with other users, by copying lines from +your terminal to theirs. +.Pp +When you run the +.Nm +command, the user you are writing to gets a message of the form: +.Pp +.Dl Message from yourname@yourhost on yourtty at hh:mm ... +.Pp +Any further lines you enter will be copied to the specified user's +terminal. +If the other user wants to reply, they must run +.Nm +as well. +.Pp +When you are done, type an end-of-file or interrupt character. +The other user will see the message +.Ql EOF +indicating that the +conversation is over. +.Pp +You can prevent people (other than the super-user) from writing to you +with the +.Xr mesg 1 +command. +Some commands, for example +.Xr nroff 1 +and +.Xr pr 1 , +disallow writing automatically, so that your output isn't overwritten. +.Pp +If the user you want to write to is logged in on more than one terminal, +you can specify which terminal to write to by specifying the terminal +name as the second operand to the +.Nm +command. +Alternatively, you can let +.Nm +select one of the terminals \- it will pick the one with the shortest +idle time. +This is so that if the user is logged in at work and also dialed up from +home, the message will go to the right place. +.Pp +The traditional protocol for writing to someone is that the string +.Ql \-o , +either at the end of a line or on a line by itself, means that it's the +other person's turn to talk. +The string +.Ql oo +means that the person believes the conversation to be +over. +.Sh SEE ALSO +.Xr mesg 1 , +.Xr talk 1 , +.Xr who 1 +.Sh HISTORY +A +.Nm +command appeared in +.At v6 . diff --git a/usr.bin/write/write.c b/usr.bin/write/write.c new file mode 100644 index 0000000..945e92f --- /dev/null +++ b/usr.bin/write/write.c @@ -0,0 +1,292 @@ +/* $NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Jef Poskanzer and Craig Leres of the Lawrence Berkeley Laboratory. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1989, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)write.c 8.2 (Berkeley) 4/27/95"; +#else +__RCSID("$NetBSD: write.c,v 1.27 2011/09/06 18:46:35 joerg Exp $"); +#endif +#endif /* not lint */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utmpentry.h" +#include "term_chk.h" + +__dead static void done(int); +static void do_write(int, const char *, const uid_t); +static void wr_fputs(char *); +static int search_utmp(char *, char *, uid_t, gid_t); +static int utmp_chk(const char *, const char *); + +int +main(int argc, char **argv) +{ + time_t atime; + uid_t myuid, uid; + int msgsok, ttyfd; + char *mytty; + gid_t saved_egid = getegid(); + + if (setegid(getgid()) == -1) + err(1, "setegid"); + myuid = getuid(); + ttyfd = -1; + + mytty = check_sender(&atime, myuid, saved_egid); + + /* check args */ + switch (argc) { + case 2: + ttyfd = search_utmp(argv[1], mytty, myuid, saved_egid); + break; + case 3: + if (!strncmp(argv[2], _PATH_DEV, strlen(_PATH_DEV))) + argv[2] += strlen(_PATH_DEV); + if (uid_from_user(argv[1], &uid) == -1) + errx(1, "%s: unknown user", argv[1]); + if (utmp_chk(argv[1], argv[2])) + errx(1, "%s is not logged in on %s", + argv[1], argv[2]); + ttyfd = term_chk(uid, argv[2], &msgsok, &atime, 0, saved_egid); + if (ttyfd == -1) + err(1, "%s%s", _PATH_DEV, argv[2]); + if (myuid && !msgsok) + errx(1, "%s has messages disabled on %s", + argv[1], argv[2]); + break; + default: + (void)fprintf(stderr, "usage: write user [tty]\n"); + exit(1); + } + if (setgid(getgid()) == -1) + err(1, "setgid"); + do_write(ttyfd, mytty, myuid); + done(0); + /* NOTREACHED */ +#ifdef __GNUC__ + return (0); +#endif +} + +/* + * utmp_chk - checks that the given user is actually logged in on + * the given tty + */ +static int +utmp_chk(const char *user, const char *tty) +{ + struct utmpentry *ep; + + (void)getutentries(NULL, &ep); + + for (; ep; ep = ep->next) + if (strcmp(user, ep->name) == 0 && strcmp(tty, ep->line) == 0) + return(0); + return(1); +} + +/* + * search_utmp - search utmp for the "best" terminal to write to + * + * Ignores terminals with messages disabled, and of the rest, returns + * the one with the most recent access time. Returns as value the number + * of the user's terminals with messages enabled, or -1 if the user is + * not logged in at all. + * + * Special case for writing to yourself - ignore the terminal you're + * writing from, unless that's the only terminal with messages enabled. + */ +static int +search_utmp(char *user, char *mytty, uid_t myuid, gid_t saved_egid) +{ + char tty[MAXPATHLEN]; + time_t bestatime, atime; + int nloggedttys, nttys, msgsok, user_is_me; + struct utmpentry *ep; + int fd, nfd; + uid_t uid; + + if (uid_from_user(user, &uid) == -1) + errx(1, "%s: unknown user", user); + + (void)getutentries(NULL, &ep); + + nloggedttys = nttys = 0; + bestatime = 0; + user_is_me = 0; + fd = -1; + for (; ep; ep = ep->next) + if (strcmp(user, ep->name) == 0) { + ++nloggedttys; + nfd = term_chk(uid, ep->line, &msgsok, &atime, 0, + saved_egid); + if (nfd == -1) + continue; /* bad term? skip */ + if (myuid && !msgsok) { + close(nfd); + continue; /* skip ttys with msgs off */ + } + if (strcmp(ep->line, mytty) == 0) { + user_is_me = 1; + if (fd == -1) + fd = nfd; + else + close(nfd); + continue; /* don't write to yourself */ + } + ++nttys; + if (atime > bestatime) { + bestatime = atime; + (void)strlcpy(tty, ep->line, sizeof(tty)); + close(fd); + fd = nfd; + } else + close(nfd); + } + + if (nloggedttys == 0) + errx(1, "%s is not logged in", user); + if (nttys == 0) { + if (user_is_me) /* ok, so write to yourself! */ + return fd; + errx(1, "%s has messages disabled", user); + } else if (nttys > 1) + warnx("%s is logged in more than once; writing to %s", + user, tty); + return fd; +} + +/* + * do_write - actually make the connection + */ +static void +do_write(int ttyfd, const char *mytty, const uid_t myuid) +{ + const char *login; + char *nows; + struct passwd *pwd; + time_t now; + char host[MAXHOSTNAMELEN + 1], line[512]; + + /* Determine our login name before we re-open stdout */ + if ((login = getlogin()) == NULL) { + if ((pwd = getpwuid(myuid)) != NULL) + login = pwd->pw_name; + else login = "???"; + } + + if (dup2(ttyfd, STDOUT_FILENO) == -1) + err(1, "dup2"); + + (void)signal(SIGINT, done); + (void)signal(SIGHUP, done); + (void)close(ttyfd); + + /* print greeting */ + if (gethostname(host, sizeof(host)) < 0) + (void)strlcpy(host, "???", sizeof(host)); + else + host[sizeof(host) - 1] = '\0'; + now = time(NULL); + nows = ctime(&now); + nows[16] = '\0'; + (void)printf("\r\n\a\a\aMessage from %s@%s on %s at %s ...\r\n", + login, host, mytty, nows + 11); + + while (fgets(line, sizeof(line), stdin) != NULL) + wr_fputs(line); +} + +/* + * done - cleanup and exit + */ +static void +done(int signo) +{ + + (void)write(STDOUT_FILENO, "EOF\r\n", sizeof("EOF\r\n") - 1); + if (signo == 0) + exit(0); + else + _exit(0); +} + +/* + * wr_fputs - like fputs(), but makes control characters visible and + * turns \n into \r\n + */ +static void +wr_fputs(char *s) +{ + unsigned char c; + +#define PUTC(c) if (putchar(c) == EOF) goto err; + + for (; *s != '\0'; ++s) { + c = toascii(*s); + if (c == '\n') { + PUTC('\r'); + } else if (!isprint(c) && !isspace(c) && c != '\a') { + PUTC('^'); + c ^= 0x40; /* DEL to ?, others to alpha */ + } + PUTC(c); + } + return; + +err: err(1, NULL); +#undef PUTC +} diff --git a/usr.bin/xargs/pathnames.h b/usr.bin/xargs/pathnames.h new file mode 100644 index 0000000..3f05b3d --- /dev/null +++ b/usr.bin/xargs/pathnames.h @@ -0,0 +1,36 @@ +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +#define _PATH_ECHO "/bin/echo" diff --git a/usr.bin/xargs/strnsubst.c b/usr.bin/xargs/strnsubst.c new file mode 100644 index 0000000..0953f49 --- /dev/null +++ b/usr.bin/xargs/strnsubst.c @@ -0,0 +1,115 @@ +/* $xMach: strnsubst.c,v 1.3 2002/02/23 02:10:24 jmallett Exp $ */ + +/* + * Copyright (c) 2002 J. Mallett. All rights reserved. + * You may do whatever you want with this file as long as + * the above copyright and this notice remain intact, along + * with the following statement: + * For the man who taught me vi, and who got too old, too young. + */ + +#include +#ifndef lint +#if 0 +__FBSDID("$FreeBSD: src/usr.bin/xargs/strnsubst.c,v 1.8 2005/12/30 23:22:50 jmallett Exp $"); +#endif +__RCSID("$NetBSD: strnsubst.c,v 1.1 2007/04/18 15:56:07 christos Exp $"); +#endif /* not lint */ + +#include +#include +#include +#include +#include + +void strnsubst(char **, const char *, const char *, size_t); + +/* + * Replaces str with a string consisting of str with match replaced with + * replstr as many times as can be done before the constructed string is + * maxsize bytes large. It does not free the string pointed to by str, it + * is up to the calling program to be sure that the original contents of + * str as well as the new contents are handled in an appropriate manner. + * If replstr is NULL, then that internally is changed to a nil-string, so + * that we can still pretend to do somewhat meaningful substitution. + * No value is returned. + */ +void +strnsubst(char **str, const char *match, const char *replstr, size_t maxsize) +{ + char *s1, *s2, *this; + + s1 = *str; + if (s1 == NULL) + return; + /* + * If maxsize is 0 then set it to to the length of s1, because we have + * to duplicate s1. XXX we maybe should double-check whether the match + * appears in s1. If it doesn't, then we also have to set the length + * to the length of s1, to avoid modifying the argument. It may make + * sense to check if maxsize is <= strlen(s1), because in that case we + * want to return the unmodified string, too. + */ + if (maxsize == 0) { + match = NULL; + maxsize = strlen(s1) + 1; + } + s2 = calloc(maxsize, 1); + if (s2 == NULL) + err(1, "calloc"); + + if (replstr == NULL) + replstr = ""; + + if (match == NULL || replstr == NULL || maxsize == strlen(s1)) { + (void)strlcpy(s2, s1, maxsize); + goto done; + } + + for (;;) { + this = strstr(s1, match); + if (this == NULL) + break; + if ((strlen(s2) + strlen(s1) + strlen(replstr) - + strlen(match) + 1) > maxsize) { + (void)strlcat(s2, s1, maxsize); + goto done; + } + (void)strncat(s2, s1, (uintptr_t)this - (uintptr_t)s1); + (void)strcat(s2, replstr); + s1 = this + strlen(match); + } + (void)strcat(s2, s1); +done: + *str = s2; + return; +} + +#ifdef TEST +#include + +int +main(void) +{ + char *x, *y, *z, *za; + + x = "{}%$"; + strnsubst(&x, "%$", "{} enpury!", 255); + y = x; + strnsubst(&y, "}{}", "ybir", 255); + z = y; + strnsubst(&z, "{", "v ", 255); + za = z; + strnsubst(&z, NULL, za, 255); + if (strcmp(z, "v ybir enpury!") == 0) + (void)printf("strnsubst() seems to work!\n"); + else + (void)printf("strnsubst() is broken.\n"); + (void)printf("%s\n", z); + free(x); + free(y); + free(z); + free(za); + return 0; +} +#endif diff --git a/usr.bin/xargs/xargs.1 b/usr.bin/xargs/xargs.1 new file mode 100644 index 0000000..d693838 --- /dev/null +++ b/usr.bin/xargs/xargs.1 @@ -0,0 +1,387 @@ +.\" $NetBSD: xargs.1,v 1.23 2012/10/13 14:18:17 njoly Exp $ +.\" +.\" Copyright (c) 1990, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" John B. Roll Jr. and the Institute of Electrical and Electronics +.\" Engineers, Inc. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)xargs.1 8.1 (Berkeley) 6/6/93 +.\" $FreeBSD: src/usr.bin/xargs/xargs.1,v 1.40 2010/12/11 08:32:16 joel Exp $ +.\" $xMach: xargs.1,v 1.2 2002/02/23 05:23:37 tim Exp $ +.\" +.Dd December 21, 2010 +.Dt XARGS 1 +.Os +.Sh NAME +.Nm xargs +.Nd "construct argument list(s) and execute utility" +.Sh SYNOPSIS +.Nm +.Op Fl 0oprt +.Op Fl E Ar eofstr +.Oo +.Fl I Ar replstr +.Op Fl R Ar replacements +.Op Fl S Ar replsize +.Oc +.Op Fl J Ar replstr +.Op Fl L Ar number +.Oo +.Fl n Ar number +.Op Fl x +.Oc +.Op Fl P Ar maxprocs +.Op Fl s Ar size +.Op Ar utility Op Ar argument ... +.Sh DESCRIPTION +The +.Nm +utility reads space, tab, newline and end-of-file delimited strings +from the standard input and executes +.Ar utility +with the strings as +arguments. +.Pp +Any arguments specified on the command line are given to +.Ar utility +upon each invocation, followed by some number of the arguments read +from the standard input of +.Nm . +This is repeated until standard input is exhausted. +.Pp +Spaces, tabs and newlines may be embedded in arguments using single +(``\ '\ '') +or double (``"'') quotes or backslashes (``\e''). +Single quotes escape all non-single quote characters, excluding newlines, +up to the matching single quote. +Double quotes escape all non-double quote characters, excluding newlines, +up to the matching double quote. +Any single character, including newlines, may be escaped by a backslash. +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl 0 +Change +.Nm +to expect NUL +(``\e0'') +characters as separators, instead of spaces and newlines. +This is expected to be used in concert with the +.Fl print0 +function in +.Xr find 1 . +.It Fl E Ar eofstr +Use +.Ar eofstr +as a logical EOF marker. +.It Fl I Ar replstr +Execute +.Ar utility +for each input line, replacing one or more occurrences of +.Ar replstr +in up to +.Ar replacements +(or 5 if no +.Fl R +flag is specified) arguments to +.Ar utility +with the entire line of input. +The resulting arguments, after replacement is done, will not be allowed to grow +beyond +.Ar replsize +(or 255 if no +.Fl S +flag is specified) +bytes; this is implemented by concatenating as much of the argument +containing +.Ar replstr +as possible, to the constructed arguments to +.Ar utility , +up to +.Ar replsize +bytes. +The size limit does not apply to arguments to +.Ar utility +which do not contain +.Ar replstr , +and furthermore, no replacement will be done on +.Ar utility +itself. +Implies +.Fl x . +.It Fl J Ar replstr +If this option is specified, +.Nm +will use the data read from standard input to replace the first occurrence of +.Ar replstr +instead of appending that data after all other arguments. +This option will not affect how many arguments will be read from input +.Pq Fl n , +or the size of the command(s) +.Nm +will generate +.Pq Fl s . +The option just moves where those arguments will be placed in the command(s) +that are executed. +The +.Ar replstr +must show up as a distinct +.Ar argument +to +.Nm . +It will not be recognized if, for instance, it is in the middle of a +quoted string. +Furthermore, only the first occurrence of the +.Ar replstr +will be replaced. +For example, the following command will copy the list of files and +directories which start with an uppercase letter in the current +directory to +.Pa destdir : +.Pp +.Dl /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir +.It Fl L Ar number +Call +.Ar utility +for every +.Ar number +lines read. +If EOF is reached and fewer lines have been read than +.Ar number +then +.Ar utility +will be called with the available lines. +.It Fl n Ar number +Set the maximum number of arguments taken from standard input for each +invocation of +.Ar utility . +An invocation of +.Ar utility +will use less than +.Ar number +standard input arguments if the number of bytes accumulated (see the +.Fl s +option) exceeds the specified +.Ar size +or there are fewer than +.Ar number +arguments remaining for the last invocation of +.Ar utility . +The current default value for +.Ar number +is 5000. +.It Fl o +Reopen stdin as +.Pa /dev/tty +in the child process before executing the command. +This is useful if you want +.Nm +to run an interactive application. +.It Fl P Ar maxprocs +Parallel mode: run at most +.Ar maxprocs +invocations of +.Ar utility +at once. +.It Fl p +Echo each command to be executed and ask the user whether it should be +executed. +An affirmative response, +.Ql y +in the POSIX locale, +causes the command to be executed, any other response causes it to be +skipped. +No commands are executed if the process is not attached to a terminal. +.It Fl r +Compatibility with GNU +.Nm . +The GNU version of +.Nm +runs the +.Ar utility +argument at least once, even if +.Nm +input is empty, and it supports a +.Fl r +option to inhibit this behavior. +The +.Nx +version of +.Nm +does not run the +.Ar utility +argument on empty input, but it supports the +.Fl r +option for command-line compatibility with GNU +.Nm ; +but the +.Fl r +option does nothing in the +.Nx +version of +.Nm . +.It Fl R Ar replacements +Specify the maximum number of arguments that +.Fl I +will do replacement in. +If +.Ar replacements +is negative, the number of arguments in which to replace is unbounded. +.It Fl S Ar replsize +Specify the amount of space (in bytes) that +.Fl I +can use for replacements. +The default for +.Ar replsize +is 255. +.It Fl s Ar size +Set the maximum number of bytes for the command line length provided to +.Ar utility . +The sum of the length of the utility name, the arguments passed to +.Ar utility +(including +.Dv NULL +terminators) and the current environment will be less than or equal to +this number. +The current default value for +.Ar size +is +.Dv ARG_MAX +- 4096. +.It Fl t +Echo the command to be executed to standard error immediately before it +is executed. +.It Fl x +Force +.Nm +to terminate immediately if a command line containing +.Ar number +arguments will not fit in the specified (or default) command line length. +.El +.Pp +If +.Ar utility +is omitted, +.Xr echo 1 +is used. +.Pp +Undefined behavior may occur if +.Ar utility +reads from the standard input. +.Pp +The +.Nm +utility exits immediately (without processing any further input) if a +command line cannot be assembled, +.Ar utility +cannot be invoked, an invocation of +.Ar utility +is terminated by a signal, +or an invocation of +.Ar utility +exits with a value of 255. +.Sh FILES +.Bl -tag -width /dev/tty -compact +.It Pa /dev/tty +used to read responses in prompt mode +.El +.Sh EXIT STATUS +.Nm +exits with one of the following values: +.Bl -tag -width Ds -compact +.It 0 +All invocations of +.Ar utility +returned a zero exit status. +.It 123 +One or more invocations of +.Ar utility +returned a nonzero exit status. +.It 124 +The +.Ar utility +exited with a 255 exit status. +.It 125 +The +.Ar utility +was killed or stopped by a signal. +.It 126 +The +.Ar utility +was found but could not be invoked. +.It 127 +The +.Ar utility +could not be found. +.It 1 +Some other error occurred. +.El +.Sh SEE ALSO +.Xr echo 1 , +.Xr find 1 , +.Xr execvp 3 +.Sh STANDARDS +The +.Nm +utility is expected to be +.St -p1003.2 +compliant. +The +.Fl J , o , P , R , +and +.Fl S +options are non-standard +.Fx +extensions which may not be available on other operating systems. +.Sh HISTORY +The +.Nm +utility appeared in PWB UNIX 1.0. +It made its first BSD appearance in the 4.3 Reno release. +.Pp +The meaning of 123, 124, and 125 exit values and the +.Fl 0 +option were taken from GNU xargs. +.Sh BUGS +If +.Ar utility +attempts to invoke another command such that the number of arguments or the +size of the environment is increased, it risks +.Xr execvp 3 +failing with +.Er E2BIG . +.Pp +The +.Nm +utility does not take multibyte characters into account when performing +string comparisons for the +.Fl I +and +.Fl J +options, which may lead to incorrect results in some locales. diff --git a/usr.bin/xargs/xargs.c b/usr.bin/xargs/xargs.c new file mode 100644 index 0000000..5c89377 --- /dev/null +++ b/usr.bin/xargs/xargs.c @@ -0,0 +1,652 @@ +/* $NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $ */ + +/*- + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * John B. Roll Jr. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $xMach: xargs.c,v 1.6 2002/02/23 05:27:47 tim Exp $ + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1990, 1993\ + The Regents of the University of California. All rights reserved."); +#if 0 +static char sccsid[] = "@(#)xargs.c 8.1 (Berkeley) 6/6/93"; +__FBSDID("$FreeBSD: src/usr.bin/xargs/xargs.c,v 1.62 2006/01/01 22:59:54 jmallett Exp $"); +#endif +__RCSID("$NetBSD: xargs.c,v 1.20 2010/12/17 11:32:57 plunky Exp $"); +#endif /* not lint */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pathnames.h" + +static void parse_input(int, char *[]); +static void prerun(int, char *[]); +static int prompt(void); +static void run(char **); +static void usage(void) __dead; +void strnsubst(char **, const char *, const char *, size_t); +static void waitchildren(const char *, int); + +static char echo[] = _PATH_ECHO; +static char **av, **bxp, **ep, **endxp, **xp; +static char *argp, *bbp, *ebp, *inpline, *p, *replstr; +static const char *eofstr; +static int count, insingle, indouble, oflag, pflag, tflag, Rflag, rval, zflag; +static int cnt, Iflag, jfound, Lflag, Sflag, wasquoted, xflag; +static int curprocs, maxprocs; + +static volatile int childerr; + +extern char **environ; + +int +main(int argc, char *argv[]) +{ + long arg_max; + int ch, Jflag, nargs, nflag, nline; + size_t linelen; + char *endptr; + + setprogname(argv[0]); + + inpline = replstr = NULL; + ep = environ; + eofstr = ""; + Jflag = nflag = 0; + + (void)setlocale(LC_ALL, ""); + + /* + * SUSv3 says of the exec family of functions: + * The number of bytes available for the new process' + * combined argument and environment lists is {ARG_MAX}. It + * is implementation-defined whether null terminators, + * pointers, and/or any alignment bytes are included in this + * total. + * + * SUSv3 says of xargs: + * ... the combined argument and environment lists ... + * shall not exceed {ARG_MAX}-2048. + * + * To be conservative, we use ARG_MAX - 4K, and we do include + * nul terminators and pointers in the calculation. + * + * Given that the smallest argument is 2 bytes in length, this + * means that the number of arguments is limited to: + * + * (ARG_MAX - 4K - LENGTH(env + utility + arguments)) / 2. + * + * We arbitrarily limit the number of arguments to 5000. This is + * allowed by POSIX.2 as long as the resulting minimum exec line is + * at least LINE_MAX. Realloc'ing as necessary is possible, but + * probably not worthwhile. + */ + nargs = 5000; + if ((arg_max = sysconf(_SC_ARG_MAX)) == -1) + errx(1, "sysconf(_SC_ARG_MAX) failed"); + nline = arg_max - 4 * 1024; + while (*ep != NULL) { + /* 1 byte for each '\0' */ + nline -= strlen(*ep++) + 1 + sizeof(*ep); + } + maxprocs = 1; + while ((ch = getopt(argc, argv, "0E:I:J:L:n:oP:pR:S:s:rtx")) != -1) + switch (ch) { + case 'E': + eofstr = optarg; + break; + case 'I': + Jflag = 0; + Iflag = 1; + Lflag = 1; + replstr = optarg; + break; + case 'J': + Iflag = 0; + Jflag = 1; + replstr = optarg; + break; + case 'L': + Lflag = atoi(optarg); + break; + case 'n': + nflag = 1; + if ((nargs = atoi(optarg)) <= 0) + errx(1, "illegal argument count"); + break; + case 'o': + oflag = 1; + break; + case 'P': + if ((maxprocs = atoi(optarg)) <= 0) + errx(1, "max. processes must be >0"); + break; + case 'p': + pflag = 1; + break; + case 'R': + Rflag = strtol(optarg, &endptr, 10); + if (*endptr != '\0') + errx(1, "replacements must be a number"); + break; + case 'r': + /* GNU compatibility */ + break; + case 'S': + Sflag = strtoul(optarg, &endptr, 10); + if (*endptr != '\0') + errx(1, "replsize must be a number"); + break; + case 's': + nline = atoi(optarg); + break; + case 't': + tflag = 1; + break; + case 'x': + xflag = 1; + break; + case '0': + zflag = 1; + break; + case '?': + default: + usage(); + } + argc -= optind; + argv += optind; + + if (!Iflag && Rflag) + usage(); + if (!Iflag && Sflag) + usage(); + if (Iflag && !Rflag) + Rflag = 5; + if (Iflag && !Sflag) + Sflag = 255; + if (xflag && !nflag) + usage(); + if (Iflag || Lflag) + xflag = 1; + if (replstr != NULL && *replstr == '\0') + errx(1, "replstr may not be empty"); + + /* + * Allocate pointers for the utility name, the utility arguments, + * the maximum arguments to be read from stdin and the trailing + * NULL. + */ + linelen = 1 + argc + nargs + 1; + if ((av = bxp = malloc(linelen * sizeof(char **))) == NULL) + errx(1, "malloc failed"); + + /* + * Use the user's name for the utility as argv[0], just like the + * shell. Echo is the default. Set up pointers for the user's + * arguments. + */ + if (*argv == NULL) + cnt = strlen(*bxp++ = echo); + else { + do { + if (Jflag && strcmp(*argv, replstr) == 0) { + char **avj; + jfound = 1; + argv++; + for (avj = argv; *avj; avj++) + cnt += strlen(*avj) + 1; + break; + } + cnt += strlen(*bxp++ = *argv) + 1; + } while (*++argv != NULL); + } + + /* + * Set up begin/end/traversing pointers into the array. The -n + * count doesn't include the trailing NULL pointer, so the malloc + * added in an extra slot. + */ + endxp = (xp = bxp) + nargs; + + /* + * Allocate buffer space for the arguments read from stdin and the + * trailing NULL. Buffer space is defined as the default or specified + * space, minus the length of the utility name and arguments. Set up + * begin/end/traversing pointers into the array. The -s count does + * include the trailing NULL, so the malloc didn't add in an extra + * slot. + */ + nline -= cnt; + if (nline <= 0) + errx(1, "insufficient space for command"); + + if ((bbp = malloc((size_t)(nline + 1))) == NULL) + errx(1, "malloc failed"); + ebp = (argp = p = bbp) + nline - 1; + for (;;) + parse_input(argc, argv); +} + +static void +parse_input(int argc, char *argv[]) +{ + int ch, foundeof; + char **avj; + + foundeof = 0; + + switch (ch = getchar()) { + case EOF: + /* No arguments since last exec. */ + if (p == bbp) { + waitchildren(*argv, 1); + exit(rval); + } + goto arg1; + case ' ': + case '\t': + /* Quotes escape tabs and spaces. */ + if (insingle || indouble || zflag) + goto addch; + goto arg2; + case '\0': + if (zflag) { + /* + * Increment 'count', so that nulls will be treated + * as end-of-line, as well as end-of-argument. This + * is needed so -0 works properly with -I and -L. + */ + count++; + goto arg2; + } + goto addch; + case '\n': + if (zflag) + goto addch; + count++; /* Indicate end-of-line (used by -L) */ + + /* Quotes do not escape newlines. */ +arg1: if (insingle || indouble) + errx(1, "unterminated quote"); +arg2: + foundeof = *eofstr != '\0' && + strncmp(argp, eofstr, (size_t)(p - argp)) == 0; + + /* Do not make empty args unless they are quoted */ + if ((argp != p || wasquoted) && !foundeof) { + *p++ = '\0'; + *xp++ = argp; + if (Iflag) { + size_t curlen; + + if (inpline == NULL) + curlen = 0; + else { + /* + * If this string is not zero + * length, append a space for + * separation before the next + * argument. + */ + if ((curlen = strlen(inpline)) != 0) + (void)strcat(inpline, " "); + } + curlen++; + /* + * Allocate enough to hold what we will + * be holding in a second, and to append + * a space next time through, if we have + * to. + */ + inpline = realloc(inpline, curlen + 2 + + strlen(argp)); + if (inpline == NULL) + errx(1, "realloc failed"); + if (curlen == 1) + (void)strcpy(inpline, argp); + else + (void)strcat(inpline, argp); + } + } + + /* + * If max'd out on args or buffer, or reached EOF, + * run the command. If xflag and max'd out on buffer + * but not on args, object. Having reached the limit + * of input lines, as specified by -L is the same as + * maxing out on arguments. + */ + if (xp == endxp || p > ebp || ch == EOF || + (Lflag <= count && xflag) || foundeof) { + if (xflag && xp != endxp && p > ebp) + errx(1, "insufficient space for arguments"); + if (jfound) { + for (avj = argv; *avj; avj++) + *xp++ = *avj; + } + prerun(argc, av); + if (ch == EOF || foundeof) { + waitchildren(*argv, 1); + exit(rval); + } + p = bbp; + xp = bxp; + count = 0; + } + argp = p; + wasquoted = 0; + break; + case '\'': + if (indouble || zflag) + goto addch; + insingle = !insingle; + wasquoted = 1; + break; + case '"': + if (insingle || zflag) + goto addch; + indouble = !indouble; + wasquoted = 1; + break; + case '\\': + if (zflag) + goto addch; + /* Backslash escapes anything, is escaped by quotes. */ + if (!insingle && !indouble && (ch = getchar()) == EOF) + errx(1, "backslash at EOF"); + /* FALLTHROUGH */ + default: +addch: if (p < ebp) { + *p++ = ch; + break; + } + + /* If only one argument, not enough buffer space. */ + if (bxp == xp) + errx(1, "insufficient space for argument"); + /* Didn't hit argument limit, so if xflag object. */ + if (xflag) + errx(1, "insufficient space for arguments"); + + if (jfound) { + for (avj = argv; *avj; avj++) + *xp++ = *avj; + } + prerun(argc, av); + xp = bxp; + cnt = ebp - argp; + (void)memcpy(bbp, argp, (size_t)cnt); + p = (argp = bbp) + cnt; + *p++ = ch; + break; + } +} + +/* + * Do things necessary before run()'ing, such as -I substitution, + * and then call run(). + */ +static void +prerun(int argc, char *argv[]) +{ + char **tmp, **tmp2, **avj; + int repls; + + repls = Rflag; + + if (argc == 0 || repls == 0) { + *xp = NULL; + run(argv); + return; + } + + avj = argv; + + /* + * Allocate memory to hold the argument list, and + * a NULL at the tail. + */ + tmp = malloc((argc + 1) * sizeof(char**)); + if (tmp == NULL) + errx(1, "malloc failed"); + tmp2 = tmp; + + /* + * Save the first argument and iterate over it, we + * cannot do strnsubst() to it. + */ + if ((*tmp++ = strdup(*avj++)) == NULL) + errx(1, "strdup failed"); + + /* + * For each argument to utility, if we have not used up + * the number of replacements we are allowed to do, and + * if the argument contains at least one occurrence of + * replstr, call strnsubst(), else just save the string. + * Iterations over elements of avj and tmp are done + * where appropriate. + */ + while (--argc) { + *tmp = *avj++; + if (repls && strstr(*tmp, replstr) != NULL) { + strnsubst(tmp++, replstr, inpline, (size_t)Sflag); + if (repls > 0) + repls--; + } else { + if ((*tmp = strdup(*tmp)) == NULL) + errx(1, "strdup failed"); + tmp++; + } + } + + /* + * Run it. + */ + *tmp = NULL; + run(tmp2); + + /* + * Walk from the tail to the head, free along the way. + */ + for (; tmp2 != tmp; tmp--) + free(*tmp); + /* + * Now free the list itself. + */ + free(tmp2); + + /* + * Free the input line buffer, if we have one. + */ + if (inpline != NULL) { + free(inpline); + inpline = NULL; + } +} + +static void +run(char **argv) +{ + int fd; + char **avec; + + /* + * If the user wants to be notified of each command before it is + * executed, notify them. If they want the notification to be + * followed by a prompt, then prompt them. + */ + if (tflag || pflag) { + (void)fprintf(stderr, "%s", *argv); + for (avec = argv + 1; *avec != NULL; ++avec) + (void)fprintf(stderr, " %s", *avec); + /* + * If the user has asked to be prompted, do so. + */ + if (pflag) + /* + * If they asked not to exec, return without execution + * but if they asked to, go to the execution. If we + * could not open their tty, break the switch and drop + * back to -t behaviour. + */ + switch (prompt()) { + case 0: + return; + case 1: + goto exec; + case 2: + break; + } + (void)fprintf(stderr, "\n"); + (void)fflush(stderr); + } +exec: + childerr = 0; + switch (vfork()) { + case -1: + err(1, "vfork"); + /*NOTREACHED*/ + case 0: + if (oflag) { + if ((fd = open(_PATH_TTY, O_RDONLY)) == -1) + err(1, "can't open /dev/tty"); + } else { + fd = open(_PATH_DEVNULL, O_RDONLY); + } + if (fd > STDIN_FILENO) { + if (dup2(fd, STDIN_FILENO) != 0) + err(1, "can't dup2 to stdin"); + (void)close(fd); + } + (void)execvp(argv[0], argv); + childerr = errno; + _exit(1); + } + curprocs++; + waitchildren(*argv, 0); +} + +static void +waitchildren(const char *name, int waitall) +{ + pid_t pid; + int status; + + while ((pid = waitpid(-1, &status, !waitall && curprocs < maxprocs ? + WNOHANG : 0)) > 0) { + curprocs--; + /* If we couldn't invoke the utility, exit. */ + if (childerr != 0) { + errno = childerr; + err(errno == ENOENT ? 127 : 126, "%s", name); + } + /* + * According to POSIX, we have to exit if the utility exits + * with a 255 status, or is interrupted by a signal. xargs + * is allowed to return any exit status between 1 and 125 + * in these cases, but we'll use 124 and 125, the same + * values used by GNU xargs. + */ + if (WIFEXITED(status)) { + if (WEXITSTATUS (status) == 255) { + warnx ("%s exited with status 255", name); + exit(124); + } else if (WEXITSTATUS (status) != 0) { + rval = 123; + } + } else if (WIFSIGNALED (status)) { + if (WTERMSIG(status) < NSIG) { + warnx("%s terminated by SIG%s", name, + sys_signame[WTERMSIG(status)]); + } else { + warnx("%s terminated by signal %d", name, + WTERMSIG(status)); + } + exit(125); + } + } + if (pid == -1 && errno != ECHILD) + err(1, "waitpid"); +} + +/* + * Prompt the user about running a command. + */ +static int +prompt(void) +{ + regex_t cre; + size_t rsize; + int match; + char *response; + FILE *ttyfp; + + if ((ttyfp = fopen(_PATH_TTY, "r")) == NULL) + return (2); /* Indicate that the TTY failed to open. */ + (void)fprintf(stderr, "?..."); + (void)fflush(stderr); + if ((response = fgetln(ttyfp, &rsize)) == NULL || + regcomp(&cre, nl_langinfo(YESEXPR), REG_BASIC) != 0) { + (void)fclose(ttyfp); + return (0); + } + response[rsize - 1] = '\0'; + match = regexec(&cre, response, 0, NULL, 0); + (void)fclose(ttyfp); + regfree(&cre); + return (match == 0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, +"Usage: %s [-0opt] [-E eofstr] [-I replstr [-R replacements] [-S replsize]]\n" +" [-J replstr] [-L number] [-n number [-x]] [-P maxprocs]\n" +" [-s size] [utility [argument ...]]\n", getprogname()); + exit(1); +} diff --git a/usr.sbin/link/link.8 b/usr.sbin/link/link.8 new file mode 100644 index 0000000..9b994ed --- /dev/null +++ b/usr.sbin/link/link.8 @@ -0,0 +1,64 @@ +.\" $NetBSD: link.8,v 1.8 2017/07/04 07:08:42 wiz Exp $ +.\" +.\" Copyright (c) 1999 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Klaus Klein. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd July 18, 1999 +.Dt LINK 8 +.Os +.Sh NAME +.Nm link +.Nd call the +.Fn link +function +.Sh SYNOPSIS +.Nm +.Ar file1 +.Ar file2 +.Sh DESCRIPTION +The +.Nm +utility performs the function call +.Fn link file1 file2 . +.Pp +.Ar file1 +must be the pathname of an existing file, and +.Ar file2 +is the pathname of the new link to +.Ar file1 +to be created. +.Sh EXIT STATUS +.Ex -std link +.Sh SEE ALSO +.Xr ln 1 , +.Xr link 2 , +.Xr unlink 8 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -xcu5 . diff --git a/usr.sbin/link/link.c b/usr.sbin/link/link.c new file mode 100644 index 0000000..6a45cc1 --- /dev/null +++ b/usr.sbin/link/link.c @@ -0,0 +1,71 @@ +/* $NetBSD: link.c,v 1.5 2011/08/30 19:18:17 joerg Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Klaus Klein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT( +"@(#) Copyright (c) 1999\ + The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: link.c,v 1.5 2011/08/30 19:18:17 joerg Exp $"); +#endif + +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + + (void)setlocale(LC_ALL, ""); + + if (argc != 3) + usage(); + + if (link(argv[1], argv[2]) != 0) { + perror("link"); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: link file1 file2\n"); + exit(EXIT_FAILURE); +} diff --git a/usr.sbin/unlink/unlink.8 b/usr.sbin/unlink/unlink.8 new file mode 100644 index 0000000..a921140 --- /dev/null +++ b/usr.sbin/unlink/unlink.8 @@ -0,0 +1,58 @@ +.\" $NetBSD: unlink.8,v 1.8 2017/07/04 07:13:43 wiz Exp $ +.\" +.\" Copyright (c) 1999 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Klaus Klein. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS +.\" ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +.\" TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +.\" PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS +.\" BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +.\" CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +.\" SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +.\" CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +.\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +.\" POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd July 18, 1998 +.Dt UNLINK 8 +.Os +.Sh NAME +.Nm unlink +.Nd call the unlink function +.Sh SYNOPSIS +.Nm +.Ar file +.Sh DESCRIPTION +The +.Nm +utility performs the function call +.Fn unlink file . +.Pp +.Ar file +must be the pathname of an existing file. +.Sh EXIT STATUS +.Ex -std unlink +.Sh SEE ALSO +.Xr rm 1 , +.Xr rmdir 1 , +.Xr unlink 2 , +.Xr link 8 +.Sh STANDARDS +The +.Nm +utility conforms to +.St -xcu5 . diff --git a/usr.sbin/unlink/unlink.c b/usr.sbin/unlink/unlink.c new file mode 100644 index 0000000..40d715d --- /dev/null +++ b/usr.sbin/unlink/unlink.c @@ -0,0 +1,70 @@ +/* $NetBSD: unlink.c,v 1.5 2011/08/30 20:50:24 joerg Exp $ */ + +/*- + * Copyright (c) 1999 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Klaus Klein. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1999\ + The NetBSD Foundation, Inc. All rights reserved."); +__RCSID("$NetBSD: unlink.c,v 1.5 2011/08/30 20:50:24 joerg Exp $"); +#endif + +#include +#include +#include +#include + +__dead static void usage(void); + +int +main(int argc, char *argv[]) +{ + + (void)setlocale(LC_ALL, ""); + + if (argc != 2) + usage(); + + if (unlink(argv[1]) != 0) { + perror("unlink"); + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); + /* NOTREACHED */ +} + +static void +usage(void) +{ + + (void)fprintf(stderr, "usage: unlink file\n"); + exit(EXIT_FAILURE); +} -- cgit v1.2.3-70-g09d2